Merge changes I00d6980a,I814221b4,Id88603a5

* changes:
  CameraBrowser: Launch itself when a camera is attached to USB.
  Send Intents when PTP compatible devices are connected/disconnected to USB
  Give system server permission to access USB.
diff --git a/Android.mk b/Android.mk
index 3afbfd7..696b117 100644
--- a/Android.mk
+++ b/Android.mk
@@ -163,6 +163,8 @@
 	core/java/com/android/internal/view/IInputMethodManager.aidl \
 	core/java/com/android/internal/view/IInputMethodSession.aidl \
 	core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
+	location/java/android/location/ICountryDetector.aidl \
+	location/java/android/location/ICountryListener.aidl \
 	location/java/android/location/IGeocodeProvider.aidl \
 	location/java/android/location/IGpsStatusListener.aidl \
 	location/java/android/location/IGpsStatusProvider.aidl \
diff --git a/api/current.xml b/api/current.xml
index a828736..f678fa2 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -2088,28 +2088,6 @@
  visibility="public"
 >
 </field>
-<field name="actionBarCloseContextDrawable"
- type="int"
- transient="false"
- volatile="false"
- value="16843550"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
-<field name="actionBarContextBackground"
- type="int"
- transient="false"
- volatile="false"
- value="16843549"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="actionButtonPadding"
  type="int"
  transient="false"
@@ -2143,6 +2121,28 @@
  visibility="public"
 >
 </field>
+<field name="actionModeBackground"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843549"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="actionModeCloseDrawable"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843550"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="activityCloseEnterAnimation"
  type="int"
  transient="false"
@@ -10525,6 +10525,17 @@
  visibility="public"
 >
 </field>
+<field name="windowActionModeOverlay"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843551"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="windowAnimationStyle"
  type="int"
  transient="false"
@@ -21849,6 +21860,19 @@
 <parameter name="args" type="android.os.Bundle">
 </parameter>
 </method>
+<method name="startActionMode"
+ return="android.view.ActionMode"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="callback" type="android.view.ActionMode.Callback">
+</parameter>
+</method>
 <method name="startActivityForResult"
  return="void"
  abstract="false"
@@ -21913,19 +21937,6 @@
 <parameter name="requestCode" type="int">
 </parameter>
 </method>
-<method name="startContextMode"
- return="android.view.ActionMode"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="callback" type="android.view.ActionMode.Callback">
-</parameter>
-</method>
 <method name="startIntentSenderForResult"
  return="void"
  abstract="false"
@@ -22143,6 +22154,17 @@
  visibility="protected"
 >
 </field>
+<field name="POP_BACK_STACK_INCLUSIVE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="RESULT_CANCELED"
  type="int"
  transient="false"
@@ -41168,6 +41190,17 @@
  visibility="public"
 >
 </field>
+<field name="DOWNLOAD_SERVICE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;download&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="DROPBOX_SERVICE"
  type="java.lang.String"
  transient="false"
@@ -96740,6 +96773,567 @@
 >
 </field>
 </class>
+<class name="DownloadManager"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="enqueue"
+ return="long"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="request" type="android.net.DownloadManager.Request">
+</parameter>
+</method>
+<method name="openDownloadedFile"
+ return="android.os.ParcelFileDescriptor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="long">
+</parameter>
+<exception name="FileNotFoundException" type="java.io.FileNotFoundException">
+</exception>
+</method>
+<method name="query"
+ return="android.database.Cursor"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="query" type="android.net.DownloadManager.Query">
+</parameter>
+</method>
+<method name="remove"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="long">
+</parameter>
+</method>
+<field name="ACTION_DOWNLOAD_COMPLETE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.action.DOWNLOAD_COMPLETE&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACTION_NOTIFICATION_CLICKED"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_BYTES_DOWNLOADED_SO_FAR"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;bytes_so_far&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_DESCRIPTION"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;description&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_ERROR_CODE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;error_code&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;id&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_LAST_MODIFIED_TIMESTAMP"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;last_modified_timestamp&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_LOCAL_URI"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;local_uri&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_MEDIA_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;media_type&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_STATUS"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;status&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_TITLE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;title&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_TOTAL_SIZE_BYTES"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;total_size&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="COLUMN_URI"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;uri&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_DEVICE_NOT_FOUND"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1007"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_FILE_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1001"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_HTTP_DATA_ERROR"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1004"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_INSUFFICIENT_SPACE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1006"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_TOO_MANY_REDIRECTS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1005"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_UNHANDLED_HTTP_CODE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1002"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ERROR_UNKNOWN"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1000"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTRA_DOWNLOAD_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;extra_download_id&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATUS_FAILED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATUS_PAUSED"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATUS_PENDING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATUS_RUNNING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="STATUS_SUCCESSFUL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="DownloadManager.Query"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="DownloadManager.Query"
+ type="android.net.DownloadManager.Query"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="setFilterById"
+ return="android.net.DownloadManager.Query"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="id" type="long">
+</parameter>
+</method>
+<method name="setFilterByStatus"
+ return="android.net.DownloadManager.Query"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+</class>
+<class name="DownloadManager.Request"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="DownloadManager.Request"
+ type="android.net.DownloadManager.Request"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</constructor>
+<method name="setAllowedNetworkTypes"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="setAllowedOverRoaming"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="allowed" type="boolean">
+</parameter>
+</method>
+<method name="setDescription"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="description" type="java.lang.String">
+</parameter>
+</method>
+<method name="setDestinationUri"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+</method>
+<method name="setMediaType"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="mediaType" type="java.lang.String">
+</parameter>
+</method>
+<method name="setRequestHeader"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="header" type="java.lang.String">
+</parameter>
+<parameter name="value" type="java.lang.String">
+</parameter>
+</method>
+<method name="setShowNotification"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="flags" type="int">
+</parameter>
+</method>
+<method name="setTitle"
+ return="android.net.DownloadManager.Request"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="title" type="java.lang.String">
+</parameter>
+</method>
+<field name="NETWORK_MOBILE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_WIFI"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NETWORK_WIMAX"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="4"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="NOTIFICATION_WHEN_RUNNING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="LocalServerSocket"
  extends="java.lang.Object"
  abstract="false"
@@ -154172,6 +154766,36 @@
 >
 </method>
 </class>
+<class name="LoaderTestCase"
+ extends="android.test.AndroidTestCase"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="LoaderTestCase"
+ type="android.test.LoaderTestCase"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getLoaderResultSynchronously"
+ return="T"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="loader" type="android.content.Loader&lt;T&gt;">
+</parameter>
+</method>
+</class>
 <class name="MoreAsserts"
  extends="java.lang.Object"
  abstract="false"
@@ -177654,6 +178278,17 @@
  visibility="public"
 >
 </method>
+<method name="getKeyboardType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getMotionRange"
  return="android.view.InputDevice.MotionRange"
  abstract="false"
@@ -177702,6 +178337,39 @@
 <parameter name="keyCode" type="int">
 </parameter>
 </method>
+<field name="KEYBOARD_TYPE_ALPHABETIC"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYBOARD_TYPE_NONE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYBOARD_TYPE_NON_ALPHABETIC"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="MOTION_RANGE_ORIENTATION"
  type="int"
  transient="false"
@@ -177801,6 +178469,17 @@
  visibility="public"
 >
 </field>
+<field name="SOURCE_ANY"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="-256"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="SOURCE_CLASS_BUTTON"
  type="int"
  transient="false"
@@ -193718,6 +194397,17 @@
  type="int"
  transient="false"
  volatile="false"
+ value="8"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FEATURE_ACTION_MODE_OVERLAY"
+ type="int"
+ transient="false"
+ volatile="false"
  value="9"
  static="true"
  final="true"
diff --git a/core/java/android/animation/AnimatableListenerAdapter.java b/core/java/android/animation/AnimatableListenerAdapter.java
new file mode 100644
index 0000000..25a842b
--- /dev/null
+++ b/core/java/android/animation/AnimatableListenerAdapter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import android.animation.Animatable.AnimatableListener;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link AnimatableListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class AnimatableListenerAdapter implements AnimatableListener {
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationCancel(Animatable animation) {
+    }
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationEnd(Animatable animation) {
+    }
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationRepeat(Animatable animation) {
+    }
+
+    /**
+     * @{inheritdoc}
+     */
+    @Override
+    public void onAnimationStart(Animatable animation) {
+    }
+
+}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 24c3c88..bf3596b 100755
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -241,25 +241,6 @@
     }
 
     /**
-     * This function is called immediately before processing the first animation
-     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
-     * function is called after that delay ends.
-     * It takes care of the final initialization steps for the
-     * animation.
-     *
-     *  <p>Overrides of this method should call the superclass method to ensure
-     *  that internal mechanisms for the animation are set up correctly.</p>
-     */
-    void initAnimation() {
-        if (mEvaluator == null) {
-            mEvaluator = (mValueType == int.class) ? sIntEvaluator :
-                (mValueType == double.class) ? sDoubleEvaluator : sFloatEvaluator;
-        }
-        mPlayingBackwards = false;
-        mCurrentIteration = 0;
-    }
-
-    /**
      * A constructor that takes <code>float</code> values.
      *
      * @param duration The length of the animation, in milliseconds.
@@ -305,6 +286,61 @@
     }
 
     /**
+     * Internal constructor that takes a single <code>float</code> value.
+     * This constructor is called by PropertyAnimator.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    Animator(long duration, float valueTo) {
+        this(duration, null, valueTo, float.class);
+    }
+
+    /**
+     * Internal constructor that takes a single <code>int</code> value.
+     * This constructor is called by PropertyAnimator.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    Animator(long duration, int valueTo) {
+        this(duration, null, valueTo, int.class);
+    }
+
+    /**
+     * Internal constructor that takes a single <code>double</code> value.
+     * This constructor is called by PropertyAnimator.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param valueFrom The initial value of the property when the animation begins.
+     * @param valueTo The value to which the property will animate.
+     */
+    Animator(long duration, double valueTo) {
+        this(duration, null, valueTo, double.class);
+    }
+
+    /**
+     * This function is called immediately before processing the first animation
+     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+     * function is called after that delay ends.
+     * It takes care of the final initialization steps for the
+     * animation.
+     *
+     *  <p>Overrides of this method should call the superclass method to ensure
+     *  that internal mechanisms for the animation are set up correctly.</p>
+     */
+    void initAnimation() {
+        if (mEvaluator == null) {
+            mEvaluator = (mValueType == int.class) ? sIntEvaluator :
+                (mValueType == double.class) ? sDoubleEvaluator : sFloatEvaluator;
+        }
+        mPlayingBackwards = false;
+        mCurrentIteration = 0;
+    }
+
+    /**
      * This custom, static handler handles the timing pulse that is shared by
      * all active animations. This approach ensures that the setting of animation
      * values will happen on the UI thread and that all animations will share
@@ -454,6 +490,9 @@
      * @return Object The starting value for the animation.
      */
     public Object getValueFrom() {
+        if (mKeyframeSet != null) {
+            return mKeyframeSet.mKeyframes.get(0).getValue();
+        }
         return mValueFrom;
     }
 
@@ -461,7 +500,12 @@
      * Sets the value that this animation will start from.
      */
     public void setValueFrom(Object valueFrom) {
-        mValueFrom = valueFrom;
+        if (mKeyframeSet != null) {
+            Keyframe kf = mKeyframeSet.mKeyframes.get(0);
+            kf.setValue(valueFrom);
+        } else {
+            mValueFrom = valueFrom;
+        }
     }
 
     /**
@@ -470,6 +514,10 @@
      * @return Object The ending value for the animation.
      */
     public Object getValueTo() {
+        if (mKeyframeSet != null) {
+            int numKeyframes = mKeyframeSet.mKeyframes.size();
+            return mKeyframeSet.mKeyframes.get(numKeyframes - 1).getValue();
+        }
         return mValueTo;
     }
 
@@ -479,7 +527,13 @@
      * @return Object The ending value for the animation.
      */
     public void setValueTo(Object valueTo) {
-        mValueTo = valueTo;
+        if (mKeyframeSet != null) {
+            int numKeyframes = mKeyframeSet.mKeyframes.size();
+            Keyframe kf = mKeyframeSet.mKeyframes.get(numKeyframes - 1);
+            kf.setValue(valueTo);
+        } else {
+            mValueTo = valueTo;
+        }
     }
 
     /**
diff --git a/core/java/android/animation/Keyframe.java b/core/java/android/animation/Keyframe.java
index b98994a..0f39635 100644
--- a/core/java/android/animation/Keyframe.java
+++ b/core/java/android/animation/Keyframe.java
@@ -142,6 +142,15 @@
     }
 
     /**
+     * Sets the value for this Keyframe.
+     *
+     * @param The value for this Keyframe.
+     */
+    public void setValue(Object value) {
+        mValue = value;
+    }
+
+    /**
      * Gets the time for this keyframe, as a fraction of the overall animation duration.
      *
      * @return The time associated with this keyframe, as a fraction of the overall animation
@@ -152,6 +161,16 @@
     }
 
     /**
+     * Sets the time for this keyframe, as a fraction of the overall animation duration.
+     *
+     * @param The time associated with this keyframe, as a fraction of the overall animation
+     * duration. This should be a value between 0 and 1.
+     */
+    public void setFraction(float fraction) {
+        mFraction = fraction;
+    }
+
+    /**
      * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
      * that there is no interpolation, which is the same as linear interpolation.
      *
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 59e9708..d144b9c 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -29,7 +29,7 @@
 
     private int mNumKeyframes;
 
-    private ArrayList<Keyframe> mKeyframes;
+    ArrayList<Keyframe> mKeyframes;
 
     public KeyframeSet(Keyframe... keyframes) {
         mKeyframes = new ArrayList<Keyframe>();
diff --git a/core/java/android/animation/PropertyAnimator.java b/core/java/android/animation/PropertyAnimator.java
index 937dd58..4b4b021 100644
--- a/core/java/android/animation/PropertyAnimator.java
+++ b/core/java/android/animation/PropertyAnimator.java
@@ -160,20 +160,23 @@
      * @param prefix "set" or "get", depending on whether we need a setter or getter.
      * @return Method the method associated with mPropertyName.
      */
-    private Method getPropertyFunction(String prefix) {
+    private Method getPropertyFunction(String prefix, Class valueType) {
         // TODO: faster implementation...
         Method returnVal = null;
         String firstLetter = mPropertyName.substring(0, 1);
         String theRest = mPropertyName.substring(1);
         firstLetter = firstLetter.toUpperCase();
         String setterName = prefix + firstLetter + theRest;
-        Class args[] = new Class[1];
-        args[0] = mValueType;
+        Class args[] = null;
+        if (valueType != null) {
+            args = new Class[1];
+            args[0] = valueType;
+        }
         try {
             returnVal = mTarget.getClass().getMethod(setterName, args);
         } catch (NoSuchMethodException e) {
             Log.e("PropertyAnimator",
-                    "Couldn't find setter for property " + mPropertyName + ": " + e);
+                    "Couldn't find setter/getter for property " + mPropertyName + ": " + e);
         }
         return returnVal;
     }
@@ -203,6 +206,31 @@
     }
 
     /**
+     * A constructor that takes a single <code>float</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, float valueTo) {
+        super(duration, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
      * A constructor that takes <code>int</code> values. When this constructor
      * is called, the system expects to find a setter for <code>propertyName</code> on
      * the target object that takes a <code>int</code> value.
@@ -227,6 +255,31 @@
     }
 
     /**
+     * A constructor that takes a single <code>int</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, int valueTo) {
+        super(duration, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
      * A constructor that takes <code>double</code> values. When this constructor
      * is called, the system expects to find a setter for <code>propertyName</code> on
      * the target object that takes a <code>double</code> value.
@@ -251,6 +304,31 @@
     }
 
     /**
+     * A constructor that takes a single <code>double</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, double valueTo) {
+        super(duration, valueTo);
+        mTarget = target;
+        mPropertyName = propertyName;
+    }
+
+    /**
      * A constructor that takes <code>Object</code> values. When this constructor
      * is called, the system expects to find a setter for <code>propertyName</code> on
      * the target object that takes a value of the same type as the <code>Object</code>s.
@@ -275,6 +353,29 @@
     }
 
     /**
+     * A constructor that takes a single <code>Object</code> value, which is the value that the
+     * target object will animate to. When this constructor
+     * is called, the system expects to find a setter for <code>propertyName</code> on
+     * the target object that takes a value of the same type as the <code>Object</code>s. The
+     * system also expects to find a similar getter function with which to derive the starting
+     * value for the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @param target The object whose property is to be animated. This object should
+     * have a public function on it called <code>setName()</code>, where <code>name</code> is
+     * the name of the property passed in as the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property on the <code>target</code> object
+     * that will be animated. Given this name, the constructor will search for a
+     * setter on the target object with the name <code>setPropertyName</code>. For example,
+     * if the constructor is called with <code>propertyName = "foo"</code>, then the
+     * target object should have a setter function with the name <code>setFoo()</code>.
+     * @param valueTo The value to which the property will animate.
+     */
+    public PropertyAnimator(int duration, Object target, String propertyName, Object valueTo) {
+        this(duration, target, propertyName, null, valueTo);
+    }
+
+    /**
      * A constructor that takes <code>Keyframe</code>s. When this constructor
      * is called, the system expects to find a setter for <code>propertyName</code> on
      * the target object that takes a value of the same type as that returned from
@@ -329,7 +430,7 @@
                         return;
                     }
                 }
-                mSetter = getPropertyFunction("set");
+                mSetter = getPropertyFunction("set", mValueType);
                 if (propertyMap == null) {
                     propertyMap = new HashMap<String, Method>();
                     sSetterPropertyMap.put(mTarget, propertyMap);
@@ -352,7 +453,7 @@
                             return;
                         }
                     }
-                    mGetter = getPropertyFunction("get");
+                    mGetter = getPropertyFunction("get", null);
                     if (propertyMap == null) {
                         propertyMap = new HashMap<String, Method>();
                         sGetterPropertyMap.put(mTarget, propertyMap);
diff --git a/core/java/android/animation/Sequencer.java b/core/java/android/animation/Sequencer.java
index 74d81e9..41ca58c 100644
--- a/core/java/android/animation/Sequencer.java
+++ b/core/java/android/animation/Sequencer.java
@@ -692,7 +692,7 @@
          */
         public void after(long delay) {
             // setup dummy Animator just to run the clock
-            Animator anim = new Animator(delay, 0, 1);
+            Animator anim = new Animator(delay, 0f, 1f);
             Node node = new Node(anim);
             mNodes.add(node);
             Dependency dependency = new Dependency(node, Dependency.AFTER);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 61fd5f3..9beaec0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2059,7 +2059,7 @@
      * removed.  Otherwise, all entries up to but not including that entry
      * will be removed
      */
-    static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
+    public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
 
     /**
      * Pop the top state off the back stack.  Returns true if there was one
@@ -4080,13 +4080,13 @@
      *
      * @see ActionMode
      */
-    public ActionMode startContextMode(ActionMode.Callback callback) {
+    public ActionMode startActionMode(ActionMode.Callback callback) {
         return mWindow.getDecorView().startActionMode(callback);
     }
 
     public ActionMode onStartActionMode(ActionMode.Callback callback) {
         if (mActionBar != null) {
-            return mActionBar.startContextMode(callback);
+            return mActionBar.startActionMode(callback);
         }
         return null;
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 8eded3d..10ebd60 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -62,6 +62,8 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.hardware.SensorManager;
+import android.location.CountryDetector;
+import android.location.ICountryDetector;
 import android.location.ILocationManager;
 import android.location.LocationManager;
 import android.media.AudioManager;
@@ -168,6 +170,7 @@
     private static ThrottleManager sThrottleManager;
     private static WifiManager sWifiManager;
     private static LocationManager sLocationManager;
+    private static CountryDetector sCountryDetector;
     private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs =
             new HashMap<File, SharedPreferencesImpl>();
 
@@ -954,6 +957,8 @@
             return AccessibilityManager.getInstance(this);
         } else if (LOCATION_SERVICE.equals(name)) {
             return getLocationManager();
+        } else if (COUNTRY_DETECTOR.equals(name)) {
+            return getCountryDetector();
         } else if (SEARCH_SERVICE.equals(name)) {
             return getSearchManager();
         } else if (SENSOR_SERVICE.equals(name)) {
@@ -1120,6 +1125,17 @@
         return sLocationManager;
     }
 
+    private CountryDetector getCountryDetector() {
+        synchronized (sSync) {
+            if (sCountryDetector == null) {
+                IBinder b = ServiceManager.getService(COUNTRY_DETECTOR);
+                ICountryDetector service = ICountryDetector.Stub.asInterface(b);
+                sCountryDetector = new CountryDetector(service);
+            }
+        }
+        return sCountryDetector;
+    }
+
     private SearchManager getSearchManager() {
         synchronized (mSync) {
             if (mSearchManager == null) {
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 697a987..b7a750b 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -400,7 +400,15 @@
      * 
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java
      *   start_compatibility}
-     * 
+     *
+     * <p class="caution">Note that the system calls this on your
+     * service's main thread.  A service's main thread is the same
+     * thread where UI operations place for Activities running in the
+     * same process.  You should always avoid stalling the main
+     * thread's event loop.  When doing long-running operations,
+     * network calls, or heavy disk I/O, you should kick off a new
+     * thread, or use {@link android.os.AsyncTask}.</p>
+     *
      * @param intent The Intent supplied to {@link android.content.Context#startService}, 
      * as given.  This may be null if the service is being restarted after
      * its process has gone away, and it had previously returned anything
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5fb2aae..9b9f796 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -57,6 +57,7 @@
  *
  * <p>The primary methods that need to be implemented are:
  * <ul>
+ *   <li>{@link #onCreate} which is called to initialize the provider</li>
  *   <li>{@link #query} which returns data to the caller</li>
  *   <li>{@link #insert} which inserts new data into the content provider</li>
  *   <li>{@link #update} which updates existing data in the content provider</li>
@@ -64,8 +65,15 @@
  *   <li>{@link #getType} which returns the MIME type of data in the content provider</li>
  * </ul></p>
  *
- * <p>This class takes care of cross process calls so subclasses don't have to worry about which
- * process a request is coming from.</p>
+ * <p class="caution">Data access methods (such as {@link #insert} and
+ * {@link #update}) may be called from many threads at once, and must be thread-safe.
+ * Other methods (such as {@link #onCreate}) are only called from the application
+ * main thread, and must avoid performing lengthy operations.  See the method
+ * descriptions for their expected thread behavior.</p>
+ *
+ * <p>Requests to {@link ContentResolver} are automatically forwarded to the appropriate
+ * ContentProvider instance, so subclasses don't have to worry about the details of
+ * cross-process calls.</p>
  */
 public abstract class ContentProvider implements ComponentCallbacks {
     /*
@@ -81,6 +89,21 @@
 
     private Transport mTransport = new Transport();
 
+    /**
+     * Construct a ContentProvider instance.  Content providers must be
+     * <a href="{@docRoot}guide/topics/manifest/provider-element.html">declared
+     * in the manifest</a>, accessed with {@link ContentResolver}, and created
+     * automatically by the system, so applications usually do not create
+     * ContentProvider instances directly.
+     *
+     * <p>At construction time, the object is uninitialized, and most fields and
+     * methods are unavailable.  Subclasses should initialize themselves in
+     * {@link #onCreate}, not the constructor.
+     *
+     * <p>Content providers are created on the application main thread at
+     * application launch time.  The constructor must not perform lengthy
+     * operations, or application startup will be delayed.
+     */
     public ContentProvider() {
     }
 
@@ -328,8 +351,8 @@
 
 
     /**
-     * Retrieve the Context this provider is running in.  Only available once
-     * onCreate(Map icicle) has been called -- this will be null in the
+     * Retrieves the Context this provider is running in.  Only available once
+     * {@link #onCreate} has been called -- this will return null in the
      * constructor.
      */
     public final Context getContext() {
@@ -403,23 +426,59 @@
     }
 
     /**
-     * Called when the provider is being started.
+     * Implement this to initialize your content provider on startup.
+     * This method is called for all registered content providers on the
+     * application main thread at application launch time.  It must not perform
+     * lengthy operations, or application startup will be delayed.
+     *
+     * <p>You should defer nontrivial initialization (such as opening,
+     * upgrading, and scanning databases) until the content provider is used
+     * (via {@link #query}, {@link #insert}, etc).  Deferred initialization
+     * keeps application startup fast, avoids unnecessary work if the provider
+     * turns out not to be needed, and stops database errors (such as a full
+     * disk) from halting application launch.
+     *
+     * <p>If you use SQLite, {@link android.database.sqlite.SQLiteOpenHelper}
+     * is a helpful utility class that makes it easy to manage databases,
+     * and will automatically defer opening until first use.  If you do use
+     * SQLiteOpenHelper, make sure to avoid calling
+     * {@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} or
+     * {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase}
+     * from this method.  (Instead, override
+     * {@link android.database.sqlite.SQLiteOpenHelper#onOpen} to initialize the
+     * database when it is first opened.)
      *
      * @return true if the provider was successfully loaded, false otherwise
      */
     public abstract boolean onCreate();
 
+    /**
+     * {@inheritDoc}
+     * This method is always called on the application main thread, and must
+     * not perform lengthy operations.
+     *
+     * <p>The default content provider implementation does nothing.
+     * Override this method to take appropriate action.
+     * (Content providers do not usually care about things like screen
+     * orientation, but may want to know about locale changes.)
+     */
     public void onConfigurationChanged(Configuration newConfig) {
     }
-    
+
+    /**
+     * {@inheritDoc}
+     * This method is always called on the application main thread, and must
+     * not perform lengthy operations.
+     *
+     * <p>The default content provider implementation does nothing.
+     * Subclasses may override this method to take appropriate action.
+     */
     public void onLowMemory() {
     }
 
     /**
-     * Receives a query request from a client in a local process, and
-     * returns a Cursor. This is called internally by the {@link ContentResolver}.
-     * This method can be called from multiple
-     * threads, as described in
+     * Implement this to handle query requests from clients.
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
      * <p>
@@ -476,11 +535,11 @@
             String selection, String[] selectionArgs, String sortOrder);
 
     /**
-     * Return the MIME type of the data at the given URI. This should start with
+     * Implement this to handle requests for the MIME type of the data at the
+     * given URI.  The returned MIME type should start with
      * <code>vnd.android.cursor.item</code> for a single record,
      * or <code>vnd.android.cursor.dir/</code> for multiple items.
-     * This method can be called from multiple
-     * threads, as described in
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
      *
@@ -490,11 +549,10 @@
     public abstract String getType(Uri uri);
 
     /**
-     * Implement this to insert a new row.
+     * Implement this to handle requests to insert a new row.
      * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
      * after inserting.
-     * This method can be called from multiple
-     * threads, as described in
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
      * @param uri The content:// URI of the insertion request.
@@ -504,12 +562,12 @@
     public abstract Uri insert(Uri uri, ContentValues values);
 
     /**
-     * Implement this to insert a set of new rows, or the default implementation will
-     * iterate over the values and call {@link #insert} on each of them.
+     * Override this to handle requests to insert a set of new rows, or the
+     * default implementation will iterate over the values and call
+     * {@link #insert} on each of them.
      * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
      * after inserting.
-     * This method can be called from multiple
-     * threads, as described in
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
      *
@@ -526,13 +584,12 @@
     }
 
     /**
-     * A request to delete one or more rows. The selection clause is applied when performing
-     * the deletion, allowing the operation to affect multiple rows in a
-     * directory.
+     * Implement this to handle requests to delete one or more rows.
+     * The implementation should apply the selection clause when performing
+     * deletion, allowing the operation to affect multiple rows in a directory.
      * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyDelete()}
      * after deleting.
-     * This method can be called from multiple
-     * threads, as described in
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
      *
@@ -549,13 +606,12 @@
     public abstract int delete(Uri uri, String selection, String[] selectionArgs);
 
     /**
-     * Update a content URI. All rows matching the optionally provided selection
-     * will have their columns listed as the keys in the values map with the
-     * values of those keys.
+     * Implement this to handle requests to update one or more rows.
+     * The implementation should update all rows matching the selection
+     * to set the columns according to the provided values map.
      * As a courtesy, call {@link ContentResolver#notifyChange(android.net.Uri ,android.database.ContentObserver) notifyChange()}
      * after updating.
-     * This method can be called from multiple
-     * threads, as described in
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
      *
@@ -570,18 +626,15 @@
             String[] selectionArgs);
 
     /**
-     * Open a file blob associated with a content URI.
-     * This method can be called from multiple
-     * threads, as described in
+     * Override this to handle requests to open a file blob.
+     * The default implementation always throws {@link FileNotFoundException}.
+     * This method can be called from multiple threads, as described in
      * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
      * Processes and Threads</a>.
-     * 
-     * <p>Returns a
-     * ParcelFileDescriptor, from which you can obtain a
-     * {@link java.io.FileDescriptor} for use with
-     * {@link java.io.FileInputStream}, {@link java.io.FileOutputStream}, etc.
-     * This can be used to store large data (such as an image) associated with
-     * a particular piece of content.
+     *
+     * <p>This method returns a ParcelFileDescriptor, which is returned directly
+     * to the caller.  This way large data (such as images and documents) can be
+     * returned without copying the content.
      *
      * <p>The returned ParcelFileDescriptor is owned by the caller, so it is
      * their responsibility to close it when done.  That is, the implementation
@@ -599,31 +652,35 @@
      * no file associated with the given URI or the mode is invalid.
      * @throws SecurityException Throws SecurityException if the caller does
      * not have permission to access the file.
-     * 
+     *
      * @see #openAssetFile(Uri, String)
      * @see #openFileHelper(Uri, String)
-     */    
+     */
     public ParcelFileDescriptor openFile(Uri uri, String mode)
             throws FileNotFoundException {
         throw new FileNotFoundException("No files supported by provider at "
                 + uri);
     }
-    
+
     /**
      * This is like {@link #openFile}, but can be implemented by providers
      * that need to be able to return sub-sections of files, often assets
-     * inside of their .apk.  Note that when implementing this your clients
-     * must be able to deal with such files, either directly with
-     * {@link ContentResolver#openAssetFileDescriptor
-     * ContentResolver.openAssetFileDescriptor}, or by using the higher-level
+     * inside of their .apk.
+     * This method can be called from multiple threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     *
+     * <p>If you implement this, your clients must be able to deal with such
+     * file slices, either directly with
+     * {@link ContentResolver#openAssetFileDescriptor}, or by using the higher-level
      * {@link ContentResolver#openInputStream ContentResolver.openInputStream}
      * or {@link ContentResolver#openOutputStream ContentResolver.openOutputStream}
      * methods.
-     * 
-     * <p><em>Note: if you are implementing this to return a full file, you
+     *
+     * <p class="note">If you are implementing this to return a full file, you
      * should create the AssetFileDescriptor with
      * {@link AssetFileDescriptor#UNKNOWN_LENGTH} to be compatible with
-     * applications that can not handle sub-sections of files.</em></p>
+     * applications that can not handle sub-sections of files.</p>
      *
      * @param uri The URI whose file is to be opened.
      * @param mode Access mode for the file.  May be "r" for read-only access,
@@ -735,17 +792,21 @@
     }
 
     /**
-     * Applies each of the {@link ContentProviderOperation} objects and returns an array
-     * of their results. Passes through OperationApplicationException, which may be thrown
-     * by the call to {@link ContentProviderOperation#apply}.
-     * If all the applications succeed then a {@link ContentProviderResult} array with the
-     * same number of elements as the operations will be returned. It is implementation-specific
-     * how many, if any, operations will have been successfully applied if a call to
-     * apply results in a {@link OperationApplicationException}.
+     * Override this to handle requests to perform a batch of operations, or the
+     * default implementation will iterate over the operations and call
+     * {@link ContentProviderOperation#apply} on each of them.
+     * If all calls to {@link ContentProviderOperation#apply} succeed
+     * then a {@link ContentProviderResult} array with as many
+     * elements as there were operations will be returned.  If any of the calls
+     * fail, it is up to the implementation how many of the others take effect.
+     * This method can be called from multiple threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     *
      * @param operations the operations to apply
      * @return the results of the applications
-     * @throws OperationApplicationException thrown if an application fails.
-     * See {@link ContentProviderOperation#apply} for more information.
+     * @throws OperationApplicationException thrown if any operation fails.
+     * @see ContentProviderOperation#apply
      */
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws OperationApplicationException {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3d64984..879103e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1380,7 +1380,16 @@
      * @see android.location.LocationManager
      */
     public static final String LOCATION_SERVICE = "location";
-    
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
+     * {@link android.location.CountryDetector} for detecting the country that
+     * the user is in.
+     *
+     * @hide
+     */
+    public static final String COUNTRY_DETECTOR = "country_detector";
+
     /**
      * Use with {@link #getSystemService} to retrieve a {@link
      * android.app.SearchManager} for handling searches.
@@ -1566,7 +1575,6 @@
      * {@link android.net.DownloadManager} for requesting HTTP downloads.
      *
      * @see #getSystemService
-     * @hide (TODO) for now
      */
     public static final String DOWNLOAD_SERVICE = "download";
 
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 0f2e872..d8cce21 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -24,10 +24,16 @@
 
 /**
  * A helper class to manage database creation and version management.
- * You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
+ *
+ * <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
  * optionally {@link #onOpen}, and this class takes care of opening the database
  * if it exists, creating it if it does not, and upgrading it as necessary.
  * Transactions are used to make sure the database is always in a sensible state.
+ *
+ * <p>This class makes it easy for {@link android.content.ContentProvider}
+ * implementations to defer opening and upgrading the database until first use,
+ * to avoid blocking application startup with long-running database upgrades.
+ *
  * <p>For an example, see the NotePadProvider class in the NotePad sample application,
  * in the <em>samples/</em> directory of the SDK.</p>
  *
@@ -51,8 +57,9 @@
 
     /**
      * Create a helper object to create, open, and/or manage a database.
-     * The database is not actually created or opened until one of
-     * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
+     * This method always returns very quickly.  The database is not actually
+     * created or opened until one of {@link #getWritableDatabase} or
+     * {@link #getReadableDatabase} is called.
      *
      * @param context to use to open or create the database
      * @param name of the database file, or null for an in-memory database
@@ -96,13 +103,20 @@
 
     /**
      * Create and/or open a database that will be used for reading and writing.
-     * Once opened successfully, the database is cached, so you can call this
-     * method every time you need to write to the database.  Make sure to call
-     * {@link #close} when you no longer need it.
+     * The first time this is called, the database will be opened and
+     * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
+     * called.
      *
-     * <p>Errors such as bad permissions or a full disk may cause this operation
+     * <p>Once opened successfully, the database is cached, so you can
+     * call this method every time you need to write to the database.
+     * (Make sure to call {@link #close} when you no longer need the database.)
+     * Errors such as bad permissions or a full disk may cause this method
      * to fail, but future attempts may succeed if the problem is fixed.</p>
      *
+     * <p class="caution">Database upgrade may take a long time, you
+     * should not call this method from the application main thread, including
+     * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
+     *
      * @throws SQLiteException if the database cannot be opened for writing
      * @return a read/write database object valid until {@link #close} is called
      */
@@ -183,6 +197,11 @@
      * database object will be closed and the read/write object will be returned
      * in the future.
      *
+     * <p class="caution">Like {@link #getWritableDatabase}, this method may
+     * take a long time to return, so you should not call it from the
+     * application main thread, including from
+     * {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
+     *
      * @throws SQLiteException if the database cannot be opened
      * @return a database object valid until {@link #getWritableDatabase}
      *     or {@link #close} is called.
@@ -262,9 +281,9 @@
     public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
 
     /**
-     * Called when the database has been opened.
-     * Override method should check {@link SQLiteDatabase#isReadOnly} before
-     * updating the database.
+     * Called when the database has been opened.  The implementation
+     * should check {@link SQLiteDatabase#isReadOnly} before updating the
+     * database.
      *
      * @param db The database.
      */
diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java
index 455bd36..1d88c18 100644
--- a/core/java/android/net/DownloadManager.java
+++ b/core/java/android/net/DownloadManager.java
@@ -42,8 +42,6 @@
  * Instances of this class should be obtained through
  * {@link android.content.Context#getSystemService(String)} by passing
  * {@link android.content.Context#DOWNLOAD_SERVICE}.
- *
- * @hide
  */
 public class DownloadManager {
     /**
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 21f711c..5f5e11c 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -97,7 +97,7 @@
         stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
         stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
     }
-    
+
     private int mNetworkType;
     private int mSubtype;
     private String mTypeName;
@@ -144,7 +144,9 @@
      * @return the network type
      */
     public int getType() {
-        return mNetworkType;
+        synchronized (this) {
+            return mNetworkType;
+        }
     }
 
     /**
@@ -153,12 +155,16 @@
      * @return the network subtype
      */
     public int getSubtype() {
-        return mSubtype;
+        synchronized (this) {
+            return mSubtype;
+        }
     }
 
     void setSubtype(int subtype, String subtypeName) {
-        mSubtype = subtype;
-        mSubtypeName = subtypeName;
+        synchronized (this) {
+            mSubtype = subtype;
+            mSubtypeName = subtypeName;
+        }
     }
 
     /**
@@ -167,7 +173,9 @@
      * @return the name of the network type
      */
     public String getTypeName() {
-        return mTypeName;
+        synchronized (this) {
+            return mTypeName;
+        }
     }
 
     /**
@@ -175,7 +183,9 @@
      * @return the name of the network subtype
      */
     public String getSubtypeName() {
-        return mSubtypeName;
+        synchronized (this) {
+            return mSubtypeName;
+        }
     }
 
     /**
@@ -188,7 +198,9 @@
      * of being established, {@code false} otherwise.
      */
     public boolean isConnectedOrConnecting() {
-        return mState == State.CONNECTED || mState == State.CONNECTING;
+        synchronized (this) {
+            return mState == State.CONNECTED || mState == State.CONNECTING;
+        }
     }
 
     /**
@@ -197,7 +209,9 @@
      * @return {@code true} if network connectivity exists, {@code false} otherwise.
      */
     public boolean isConnected() {
-        return mState == State.CONNECTED;
+        synchronized (this) {
+            return mState == State.CONNECTED;
+        }
     }
 
     /**
@@ -213,7 +227,9 @@
      * @return {@code true} if the network is available, {@code false} otherwise
      */
     public boolean isAvailable() {
-        return mIsAvailable;
+        synchronized (this) {
+            return mIsAvailable;
+        }
     }
 
     /**
@@ -223,7 +239,9 @@
      * @hide
      */
     public void setIsAvailable(boolean isAvailable) {
-        mIsAvailable = isAvailable;
+        synchronized (this) {
+            mIsAvailable = isAvailable;
+        }
     }
 
     /**
@@ -234,7 +252,9 @@
      * otherwise.
      */
     public boolean isFailover() {
-        return mIsFailover;
+        synchronized (this) {
+            return mIsFailover;
+        }
     }
 
     /**
@@ -244,7 +264,9 @@
      * @hide
      */
     public void setFailover(boolean isFailover) {
-        mIsFailover = isFailover;
+        synchronized (this) {
+            mIsFailover = isFailover;
+        }
     }
 
     /**
@@ -254,11 +276,15 @@
      * @return {@code true} if roaming is in effect, {@code false} otherwise.
      */
     public boolean isRoaming() {
-        return mIsRoaming;
+        synchronized (this) {
+            return mIsRoaming;
+        }
     }
 
     void setRoaming(boolean isRoaming) {
-        mIsRoaming = isRoaming;
+        synchronized (this) {
+            mIsRoaming = isRoaming;
+        }
     }
 
     /**
@@ -266,7 +292,9 @@
      * @return the coarse-grained state
      */
     public State getState() {
-        return mState;
+        synchronized (this) {
+            return mState;
+        }
     }
 
     /**
@@ -274,7 +302,9 @@
      * @return the fine-grained state
      */
     public DetailedState getDetailedState() {
-        return mDetailedState;
+        synchronized (this) {
+            return mDetailedState;
+        }
     }
 
     /**
@@ -287,10 +317,12 @@
      * @hide
      */
     public void setDetailedState(DetailedState detailedState, String reason, String extraInfo) {
-        this.mDetailedState = detailedState;
-        this.mState = stateMap.get(detailedState);
-        this.mReason = reason;
-        this.mExtraInfo = extraInfo;
+        synchronized (this) {
+            this.mDetailedState = detailedState;
+            this.mState = stateMap.get(detailedState);
+            this.mReason = reason;
+            this.mExtraInfo = extraInfo;
+        }
     }
 
     /**
@@ -299,7 +331,9 @@
      * @return the reason for failure, or null if not available
      */
     public String getReason() {
-        return mReason;
+        synchronized (this) {
+            return mReason;
+        }
     }
 
     /**
@@ -309,20 +343,24 @@
      * @return the extra information, or null if not available
      */
     public String getExtraInfo() {
-        return mExtraInfo;
+        synchronized (this) {
+            return mExtraInfo;
+        }
     }
 
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder("NetworkInfo: ");
-        builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
-                append("], state: ").append(mState).append("/").append(mDetailedState).
-                append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
-                append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
-                append(", roaming: ").append(mIsRoaming).
-                append(", failover: ").append(mIsFailover).
-                append(", isAvailable: ").append(mIsAvailable);
-        return builder.toString();
+        synchronized (this) {
+            StringBuilder builder = new StringBuilder("NetworkInfo: ");
+            builder.append("type: ").append(getTypeName()).append("[").append(getSubtypeName()).
+            append("], state: ").append(mState).append("/").append(mDetailedState).
+            append(", reason: ").append(mReason == null ? "(unspecified)" : mReason).
+            append(", extra: ").append(mExtraInfo == null ? "(none)" : mExtraInfo).
+            append(", roaming: ").append(mIsRoaming).
+            append(", failover: ").append(mIsFailover).
+            append(", isAvailable: ").append(mIsAvailable);
+            return builder.toString();
+        }
     }
 
     /**
@@ -338,17 +376,19 @@
      * @hide
      */
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mNetworkType);
-        dest.writeInt(mSubtype);
-        dest.writeString(mTypeName);
-        dest.writeString(mSubtypeName);
-        dest.writeString(mState.name());
-        dest.writeString(mDetailedState.name());
-        dest.writeInt(mIsFailover ? 1 : 0);
-        dest.writeInt(mIsAvailable ? 1 : 0);
-        dest.writeInt(mIsRoaming ? 1 : 0);
-        dest.writeString(mReason);
-        dest.writeString(mExtraInfo);
+        synchronized (this) {
+            dest.writeInt(mNetworkType);
+            dest.writeInt(mSubtype);
+            dest.writeString(mTypeName);
+            dest.writeString(mSubtypeName);
+            dest.writeString(mState.name());
+            dest.writeString(mDetailedState.name());
+            dest.writeInt(mIsFailover ? 1 : 0);
+            dest.writeInt(mIsAvailable ? 1 : 0);
+            dest.writeInt(mIsRoaming ? 1 : 0);
+            dest.writeString(mReason);
+            dest.writeString(mExtraInfo);
+        }
     }
 
     /**
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index dc92590..d4b0500 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -97,7 +97,7 @@
      * via Parcel.writeNoException() (amusingly) where the caller can
      * choose how to react.
      */
-    private static ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations =
+    private static final ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations =
             new ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>>() {
         @Override protected ArrayList<ApplicationErrorReport.CrashInfo> initialValue() {
             // Starts null to avoid unnecessary allocations when
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 21c2976..d5b2121 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -30,11 +30,13 @@
  * As a further wrinkle, different kinds of input sources uses different coordinate systems
  * to describe motion events.  Refer to the comments on the input source constants for
  * the appropriate interpretation.
+ * </p>
  */
 public final class InputDevice {
     private int mId;
     private String mName;
     private int mSources;
+    private int mKeyboardType;
     
     /**
      * A mask for input source classes.
@@ -173,6 +175,12 @@
      * @see #SOURCE_CLASS_JOYSTICK
      */
     public static final int SOURCE_JOYSTICK_RIGHT = 0x02000000 | SOURCE_CLASS_JOYSTICK;
+    
+    /**
+     * A special input source constant that is used when filtering input devices
+     * to match devices that provide any type of input source.
+     */
+    public static final int SOURCE_ANY = 0xffffff00;
 
     /**
      * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#x}.
@@ -237,6 +245,22 @@
      * @see #getMotionRange
      */
     public static final int MOTION_RANGE_ORIENTATION = 8;
+    
+    /**
+     * There is no keyboard.
+     */
+    public static final int KEYBOARD_TYPE_NONE = 0;
+    
+    /**
+     * The keyboard is not fully alphabetic.  It may be a numeric keypad or an assortment
+     * of buttons that are not mapped as alphabetic keys suitable for text input.
+     */
+    public static final int KEYBOARD_TYPE_NON_ALPHABETIC = 1;
+    
+    /**
+     * The keyboard supports a complement of alphabetic keys.
+     */
+    public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
 
     /**
      * Gets information about the input device with the specified id.
@@ -265,6 +289,14 @@
     }
     
     /**
+     * Gets the keyboard type.
+     * @return The keyboard type.
+     */
+    public int getKeyboardType() {
+        return mKeyboardType;
+    }
+    
+    /**
      * Gets the key character map associated with this input device.
      * @return The key character map.
      */
diff --git a/core/java/android/view/MenuInflater.java b/core/java/android/view/MenuInflater.java
index 4a966b5..a959e0d 100644
--- a/core/java/android/view/MenuInflater.java
+++ b/core/java/android/view/MenuInflater.java
@@ -350,6 +350,10 @@
                 .setShowAsAction(itemShowAsAction);
             
             if (itemListenerMethodName != null) {
+                if (mContext.isRestricted()) {
+                    throw new IllegalStateException("The android:onClick attribute cannot "
+                            + "be used within a restricted context");
+                }
                 item.setOnMenuItemClickListener(
                         new InflatedOnMenuItemClickListener(mContext, itemListenerMethodName));
             }
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 67bce99..c7be751 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -181,61 +181,61 @@
      * Offset for the sample's X coordinate.
      * @hide
      */
-    static public final int SAMPLE_X = 0;
+    static private final int SAMPLE_X = 0;
     
     /**
      * Offset for the sample's Y coordinate.
      * @hide
      */
-    static public final int SAMPLE_Y = 1;
+    static private final int SAMPLE_Y = 1;
     
     /**
      * Offset for the sample's pressure.
      * @hide
      */
-    static public final int SAMPLE_PRESSURE = 2;
+    static private final int SAMPLE_PRESSURE = 2;
     
     /**
      * Offset for the sample's size
      * @hide
      */
-    static public final int SAMPLE_SIZE = 3;
+    static private final int SAMPLE_SIZE = 3;
     
     /**
      * Offset for the sample's touch major axis length.
      * @hide
      */
-    static public final int SAMPLE_TOUCH_MAJOR = 4;
+    static private final int SAMPLE_TOUCH_MAJOR = 4;
 
     /**
      * Offset for the sample's touch minor axis length.
      * @hide
      */
-    static public final int SAMPLE_TOUCH_MINOR = 5;
+    static private final int SAMPLE_TOUCH_MINOR = 5;
     
     /**
      * Offset for the sample's tool major axis length.
      * @hide
      */
-    static public final int SAMPLE_TOOL_MAJOR = 6;
+    static private final int SAMPLE_TOOL_MAJOR = 6;
 
     /**
      * Offset for the sample's tool minor axis length.
      * @hide
      */
-    static public final int SAMPLE_TOOL_MINOR = 7;
+    static private final int SAMPLE_TOOL_MINOR = 7;
     
     /**
      * Offset for the sample's orientation.
      * @hide
      */
-    static public final int SAMPLE_ORIENTATION = 8;
+    static private final int SAMPLE_ORIENTATION = 8;
 
     /**
      * Number of data items for each sample.
      * @hide
      */
-    static public final int NUM_SAMPLE_DATA = 9;
+    static private final int NUM_SAMPLE_DATA = 9;
     
     /**
      * Number of possible pointers.
@@ -918,7 +918,7 @@
      * upwards, is perfectly circular or is of unknown orientation.  A positive angle
      * indicates that the major axis of contact is oriented to the right.  A negative angle
      * indicates that the major axis of contact is oriented to the left.
-     * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians
+     * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
      * (finger pointing fully right).
      * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
      * (the first pointer that is down) to {@link #getPointerCount()}-1.
@@ -1614,28 +1614,28 @@
          * upwards, is perfectly circular or is of unknown orientation.  A positive angle
          * indicates that the major axis of contact is oriented to the right.  A negative angle
          * indicates that the major axis of contact is oriented to the left.
-         * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians
+         * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
          * (finger pointing fully right).
          */
         public float orientation;
         
         /*
-        private static final float PI_8 = (float) (Math.PI / 8);
+        private static final float PI_4 = (float) (Math.PI / 4);
         
         public float getTouchWidth() {
-            return Math.abs(orientation) > PI_8 ? touchMajor : touchMinor;
+            return Math.abs(orientation) > PI_4 ? touchMajor : touchMinor;
         }
         
         public float getTouchHeight() {
-            return Math.abs(orientation) > PI_8 ? touchMinor : touchMajor;
+            return Math.abs(orientation) > PI_4 ? touchMinor : touchMajor;
         }
         
         public float getToolWidth() {
-            return Math.abs(orientation) > PI_8 ? toolMajor : toolMinor;
+            return Math.abs(orientation) > PI_4 ? toolMajor : toolMinor;
         }
         
         public float getToolHeight() {
-            return Math.abs(orientation) > PI_8 ? toolMinor : toolMajor;
+            return Math.abs(orientation) > PI_4 ? toolMinor : toolMajor;
         }
         */
     }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index ed02456..0d4e84b 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -56,18 +56,18 @@
     public static final int FEATURE_CONTEXT_MENU = 6;
     /** Flag for custom title. You cannot combine this feature with other title features. */
     public static final int FEATURE_CUSTOM_TITLE = 7;
-    /** Flag for asking for an OpenGL enabled window.
-        All 2D graphics will be handled by OpenGL ES.
-        @hide
-    */
-    public static final int FEATURE_OPENGL = 8;
     /**
      * Flag for enabling the Action Bar.
      * This is enabled by default for some devices. The Action Bar
      * replaces the title bar and provides an alternate location
      * for an on-screen menu button on some devices.
      */
-    public static final int FEATURE_ACTION_BAR = 9;
+    public static final int FEATURE_ACTION_BAR = 8;
+    /**
+     * Flag for specifying the behavior of action modes when an Action Bar is not present.
+     * If overlay is enabled, the action mode UI will be allowed to cover existing window content.
+     */
+    public static final int FEATURE_ACTION_MODE_OVERLAY = 9;
     /** Flag for setting the progress bar's visibility to VISIBLE */
     public static final int PROGRESS_VISIBILITY_ON = -1;
     /** Flag for setting the progress bar's visibility to GONE */
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 52e992b..681ef70 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -19,12 +19,11 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
 import android.util.EventLog;
-import java.lang.SecurityException;
+
 import java.util.Locale;
 
 /**
@@ -433,9 +432,8 @@
             buffer.append(" Build/");
             buffer.append(id);
         }
-        String mobile = ((mContext.getResources().getConfiguration().screenLayout
-                & Configuration.SCREENLAYOUT_SIZE_MASK)
-                == Configuration.SCREENLAYOUT_SIZE_XLARGE) ? "" : "Mobile ";
+        String mobile = mContext.getResources().getText(
+            com.android.internal.R.string.web_user_agent_target_content).toString();
         final String base = mContext.getResources().getText(
                 com.android.internal.R.string.web_user_agent).toString();
         return String.format(base, buffer, mobile);
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 6515f25..e457701 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -66,7 +66,7 @@
     private TabImpl mSelectedTab;
     private int mTabSwitchMode = TAB_SWITCH_ADD_REMOVE;
     
-    private ActionMode mContextMode;
+    private ActionMode mActionMode;
     
     private static final int CONTEXT_DISPLAY_NORMAL = 0;
     private static final int CONTEXT_DISPLAY_SPLIT = 1;
@@ -229,9 +229,9 @@
         return mActionView.getDisplayOptions();
     }
 
-    public ActionMode startContextMode(ActionMode.Callback callback) {
-        if (mContextMode != null) {
-            mContextMode.finish();
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        if (mActionMode != null) {
+            mActionMode.finish();
         }
 
         // Don't wait for the close context mode animation to finish.
@@ -241,7 +241,7 @@
             mCloseContext.run();
         }
 
-        ActionMode mode = new ContextModeImpl(callback);
+        ActionMode mode = new ActionModeImpl(callback);
         if (callback.onCreateActionMode(mode, mode.getMenu())) {
             mode.invalidate();
             mUpperContextView.initForMode(mode);
@@ -250,7 +250,7 @@
                 // TODO animate this
                 mLowerContextView.setVisibility(View.VISIBLE);
             }
-            mContextMode = mode;
+            mActionMode = mode;
             return mode;
         }
         return null;
@@ -361,12 +361,12 @@
     /**
      * @hide 
      */
-    public class ContextModeImpl extends ActionMode implements MenuBuilder.Callback {
+    public class ActionModeImpl extends ActionMode implements MenuBuilder.Callback {
         private ActionMode.Callback mCallback;
         private MenuBuilder mMenu;
         private WeakReference<View> mCustomView;
         
-        public ContextModeImpl(ActionMode.Callback callback) {
+        public ActionModeImpl(ActionMode.Callback callback) {
             mCallback = callback;
             mMenu = new MenuBuilder(mActionView.getContext());
             mMenu.setCallback(this);
@@ -379,8 +379,8 @@
 
         @Override
         public void finish() {
-            if (mContextMode != this) {
-                // Not the active context mode - no-op
+            if (mActionMode != this) {
+                // Not the active action mode - no-op
                 return;
             }
 
@@ -395,7 +395,7 @@
                 // TODO Animate this
                 mLowerContextView.setVisibility(View.GONE);
             }
-            mContextMode = null;
+            mActionMode = null;
         }
 
         @Override
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
new file mode 100644
index 0000000..13eda10
--- /dev/null
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.view;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.ActionBarContextView;
+
+import android.content.Context;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+
+public class StandaloneActionMode extends ActionMode implements MenuBuilder.Callback {
+    private Context mContext;
+    private ActionBarContextView mContextView;
+    private ActionMode.Callback mCallback;
+    private WeakReference<View> mCustomView;
+    private boolean mFinished;
+
+    private MenuBuilder mMenu;
+
+    public StandaloneActionMode(Context context, ActionBarContextView view,
+            ActionMode.Callback callback) {
+        mContext = context;
+        mContextView = view;
+        mCallback = callback;
+
+        mMenu = new MenuBuilder(context);
+        mMenu.setCallback(this);
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        mContextView.setTitle(title);
+    }
+
+    @Override
+    public void setSubtitle(CharSequence subtitle) {
+        mContextView.setSubtitle(subtitle);
+    }
+
+    @Override
+    public void setCustomView(View view) {
+        mContextView.setCustomView(view);
+        mCustomView = view != null ? new WeakReference<View>(view) : null;
+    }
+
+    @Override
+    public void invalidate() {
+        mCallback.onPrepareActionMode(this, mMenu);
+    }
+
+    @Override
+    public void finish() {
+        if (mFinished) {
+            return;
+        }
+        mFinished = true;
+
+        mCallback.onDestroyActionMode(this);
+        mContextView.setVisibility(View.GONE);
+    }
+
+    @Override
+    public Menu getMenu() {
+        return mMenu;
+    }
+
+    @Override
+    public CharSequence getTitle() {
+        return mContextView.getTitle();
+    }
+
+    @Override
+    public CharSequence getSubtitle() {
+        return mContextView.getSubtitle();
+    }
+
+    @Override
+    public View getCustomView() {
+        return mCustomView != null ? mCustomView.get() : null;
+    }
+
+    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+        return mCallback.onActionItemClicked(this, item);
+    }
+
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+    }
+
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) {
+            return true;
+        }
+
+        new MenuPopupHelper(mContext, subMenu).show();
+        return true;
+    }
+
+    public void onCloseSubMenu(SubMenuBuilder menu) {
+    }
+
+    public void onMenuModeChange(MenuBuilder menu) {
+    }
+}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index a185e21..0bedc4e 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -70,9 +70,9 @@
         mItemPadding = a.getDimensionPixelOffset(
                 com.android.internal.R.styleable.Theme_actionButtonPadding, 0);
         setBackgroundDrawable(a.getDrawable(
-                com.android.internal.R.styleable.Theme_actionBarContextBackground));
+                com.android.internal.R.styleable.Theme_actionModeBackground));
         mCloseDrawable = a.getDrawable(
-                com.android.internal.R.styleable.Theme_actionBarCloseContextDrawable);
+                com.android.internal.R.styleable.Theme_actionModeCloseDrawable);
         mItemMargin = mItemPadding / 2;
 
         mContentHeight =
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index 7392442..aec537d 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -98,6 +98,11 @@
     }
 }
 
+static jboolean android_net_wifi_isDriverLoaded(JNIEnv* env, jobject clazz)
+{
+    return (jboolean)(::is_wifi_driver_loaded() == 1);
+}
+
 static jboolean android_net_wifi_loadDriver(JNIEnv* env, jobject clazz)
 {
     return (jboolean)(::wifi_load_driver() == 0);
@@ -524,6 +529,7 @@
     /* name, signature, funcPtr */
 
     { "loadDriver", "()Z",  (void *)android_net_wifi_loadDriver },
+    { "isDriverLoaded", "()Z",  (void *)android_net_wifi_isDriverLoaded},
     { "unloadDriver", "()Z",  (void *)android_net_wifi_unloadDriver },
     { "startSupplicant", "()Z",  (void *)android_net_wifi_startSupplicant },
     { "stopSupplicant", "()Z",  (void *)android_net_wifi_stopSupplicant },
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 040b324..5c4e4fd 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1566,15 +1566,15 @@
     if (parcel != NULL) {
         const jchar* str = env->GetStringCritical(name, 0);
         if (str) {
-            const int32_t old_strict_policy =
-                IPCThreadState::self()->getStrictModePolicy();
-            int32_t strict_policy;
-            bool isValid = parcel->enforceInterface(
+            IPCThreadState* threadState = IPCThreadState::self();
+            const int32_t oldPolicy = threadState->getStrictModePolicy();
+            const bool isValid = parcel->enforceInterface(
                 String16(str, env->GetStringLength(name)),
-                &strict_policy);
+                threadState);
             env->ReleaseStringCritical(name, str);
             if (isValid) {
-                if (old_strict_policy != strict_policy) {
+                const int32_t newPolicy = threadState->getStrictModePolicy();
+                if (oldPolicy != newPolicy) {
                     // Need to keep the Java-level thread-local strict
                     // mode policy in sync for the libcore
                     // enforcements, which involves an upcall back
@@ -1582,7 +1582,7 @@
                     // Parcel.enforceInterface signature, as it's
                     // pseudo-public, and used via AIDL
                     // auto-generation...)
-                    set_dalvik_blockguard_policy(env, strict_policy);
+                    set_dalvik_blockguard_policy(env, newPolicy);
                 }
                 return;     // everything was correct -> return silently
             }
diff --git a/core/res/res/layout/screen_xlarge_action_bar.xml b/core/res/res/layout-xlarge/screen_action_bar.xml
similarity index 96%
rename from core/res/res/layout/screen_xlarge_action_bar.xml
rename to core/res/res/layout-xlarge/screen_action_bar.xml
index c51ca13..41ce8e4 100644
--- a/core/res/res/layout/screen_xlarge_action_bar.xml
+++ b/core/res/res/layout-xlarge/screen_action_bar.xml
@@ -4,9 +4,9 @@
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
      You may obtain a copy of the License at
-  
+
           http://www.apache.org/licenses/LICENSE-2.0
-  
+
      Unless required by applicable law or agreed to in writing, software
      distributed under the License is distributed on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -37,7 +37,7 @@
             android:layout_height="wrap_content" />
     </ViewAnimator>
     <FrameLayout android:id="@android:id/content"
-        android:layout_width="match_parent" 
+        android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1"
         android:foregroundGravity="fill_horizontal|top"
diff --git a/core/res/res/layout/screen.xml b/core/res/res/layout/screen.xml
index dfa9731..0269bf1 100644
--- a/core/res/res/layout/screen.xml
+++ b/core/res/res/layout/screen.xml
@@ -25,6 +25,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
 >
+    <!-- Popout bar for action modes -->
+    <com.android.internal.widget.ActionBarContextView
+         android:id="@+id/action_mode_bar"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:visibility="gone" />
+
     <!-- Title bar -->
     <RelativeLayout android:id="@android:id/title_container"
         style="?android:attr/windowTitleBackgroundStyle"
diff --git a/core/res/res/layout/screen_custom_title.xml b/core/res/res/layout/screen_custom_title.xml
index 34c9de0..fe06ddd 100644
--- a/core/res/res/layout/screen_custom_title.xml
+++ b/core/res/res/layout/screen_custom_title.xml
@@ -21,6 +21,13 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:fitsSystemWindows="true">
+    <!-- Popout bar for action modes -->
+    <com.android.internal.widget.ActionBarContextView
+         android:id="@+id/action_mode_bar"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:visibility="gone" />
+
     <FrameLayout android:id="@android:id/title_container" 
         android:layout_width="match_parent" 
         android:layout_height="?android:attr/windowTitleSize"
diff --git a/core/res/res/layout/screen_progress.xml b/core/res/res/layout/screen_progress.xml
index 6e694c1..9669533 100644
--- a/core/res/res/layout/screen_progress.xml
+++ b/core/res/res/layout/screen_progress.xml
@@ -26,6 +26,13 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
 >
+    <!-- Popout bar for action modes -->
+    <com.android.internal.widget.ActionBarContextView
+         android:id="@+id/action_mode_bar"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:visibility="gone" />
+
     <RelativeLayout android:id="@android:id/title_container" 
         style="?android:attr/windowTitleBackgroundStyle"
         android:layout_width="match_parent" 
diff --git a/core/res/res/layout/screen_title.xml b/core/res/res/layout/screen_title.xml
index 39ea131..26597af 100644
--- a/core/res/res/layout/screen_title.xml
+++ b/core/res/res/layout/screen_title.xml
@@ -22,6 +22,12 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
     android:fitsSystemWindows="true">
+    <!-- Popout bar for action modes -->
+    <com.android.internal.widget.ActionBarContextView
+         android:id="@+id/action_mode_bar"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:visibility="gone" />
     <FrameLayout
         android:layout_width="match_parent" 
         android:layout_height="?android:attr/windowTitleSize"
diff --git a/core/res/res/layout/screen_title_icons.xml b/core/res/res/layout/screen_title_icons.xml
index 62a0228..c8cf334 100644
--- a/core/res/res/layout/screen_title_icons.xml
+++ b/core/res/res/layout/screen_title_icons.xml
@@ -23,6 +23,12 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
+    <!-- Popout bar for action modes -->
+    <com.android.internal.widget.ActionBarContextView
+         android:id="@+id/action_mode_bar"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:visibility="gone" />
     <RelativeLayout android:id="@android:id/title_container"
         style="?android:attr/windowTitleBackgroundStyle"
         android:layout_width="match_parent"
diff --git a/core/res/res/values-xlarge/strings.xml b/core/res/res/values-xlarge/strings.xml
new file mode 100644
index 0000000..fc20be6
--- /dev/null
+++ b/core/res/res/values-xlarge/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/assets/res/any/strings.xml
+**
+** Copyright 2010, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Do not translate.  WebView User Agent targeted content -->
+    <string name="web_user_agent_target_content" translatable="false"></string>
+
+</resources>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4edd964..d1024fe 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -251,6 +251,10 @@
         <!-- Reference to a style for the Action Bar -->
         <attr name="windowActionBarStyle" format="reference" />
 
+        <!-- Flag indicating whether action modes should overlay window content
+             when there is not reserved space for their UI (such as an Action Bar). -->
+        <attr name="windowActionModeOverlay" format="boolean" />
+
         <!-- Defines the default soft input state that this window would
              like when it is displayed. -->
         <attr name="windowSoftInputMode">
@@ -444,8 +448,15 @@
         <eat-comment />
         <!-- Default amount of padding to use between action buttons. -->
         <attr name="actionButtonPadding" format="dimension" />
-        <attr name="actionBarContextBackground" format="reference" />
-        <attr name="actionBarCloseContextDrawable" format="reference" />
+
+        <!-- =================== -->
+        <!-- Action mode styles  -->
+        <!-- =================== -->
+        <eat-comment />
+        <!-- Background drawable to use for action mode UI -->
+        <attr name="actionModeBackground" format="reference" />
+        <!-- Drawable to use for the close action mode button -->
+        <attr name="actionModeCloseDrawable" format="reference" />
 
         <!-- =================== -->
         <!-- Preference styles   -->
@@ -987,6 +998,7 @@
         <attr name="backgroundDimAmount" />
         <attr name="windowActionBar" />
         <attr name="windowActionBarStyle" />
+        <attr name="windowActionModeOverlay" />
     </declare-styleable>
 
     <!-- The set of attributes that describe a AlertDialog's theme. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 34dd46c..f1ff3a3 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1306,8 +1306,9 @@
   <public type="attr" name="showAsAction" />
   <public type="attr" name="actionButtonPadding" />
   <public type="attr" name="previewImage" />
-  <public type="attr" name="actionBarContextBackground" />
-  <public type="attr" name="actionBarCloseContextDrawable" />
+  <public type="attr" name="actionModeBackground" />
+  <public type="attr" name="actionModeCloseDrawable" />
+  <public type="attr" name="windowActionModeOverlay" />
 
   <public type="id" name="home" />
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7a80884..04a8b04 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1605,7 +1605,9 @@
 
     <!-- Do not translate.  WebView User Agent string -->
     <string name="web_user_agent" translatable="false">Mozilla/5.0 (Linux; U; <xliff:g id="x">Android %s</xliff:g>)
-        AppleWebKit/534.3 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.3</string>
+        AppleWebKit/534.1 (KHTML, like Gecko) Version/4.0 <xliff:g id="mobile">%s</xliff:g>Safari/534.1</string>
+    <!-- Do not translate.  WebView User Agent targeted content -->
+    <string name="web_user_agent_target_content" translatable="false">"Mobile "</string>
 
     <!-- Title for a JavaScript dialog. "The page at <url of current page> says:" -->
     <string name="js_dialog_title">The page at \'<xliff:g id="title">%s</xliff:g>\' says:</string>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index bce0e02..998480d 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -206,8 +206,8 @@
 
         <!-- Action bar styles -->
         <item name="actionButtonPadding">12dip</item>
-        <item name="actionBarContextBackground">@android:drawable/action_bar_context_background</item>
-        <item name="actionBarCloseContextDrawable">@android:drawable/ic_menu_close_clear_cancel</item>
+        <item name="actionModeBackground">@android:drawable/action_bar_context_background</item>
+        <item name="actionModeCloseDrawable">@android:drawable/ic_menu_close_clear_cancel</item>
     </style>
     
     <!-- Variant of the default (dark) theme with no title bar -->
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
index cdaefc8..ad8d444 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
@@ -91,30 +91,30 @@
     //                                      DISCONNECTING, DISCONNECTED, UNKNOWN
     private void waitForNetworkState(int networkType, State expectedState, long timeout) {
         long startTime = System.currentTimeMillis();
-        // In case the broadcast is already sent out, no need to wait
-        if (cmActivity.mCM.getNetworkInfo(networkType).getState() == expectedState) {
-            return;
-        } else {
-            while (true) {
-                if ((System.currentTimeMillis() - startTime) > timeout) {
+        while (true) {
+            if ((System.currentTimeMillis() - startTime) > timeout) {
+                if (cmActivity.mCM.getNetworkInfo(networkType).getState() != expectedState) {
                     assertFalse("Wait for network state timeout", true);
+                } else {
+                    // the broadcast has been sent out. the state has been changed.
+                    return;
                 }
-                Log.v(LOG_TAG, "Wait for the connectivity state for network: " + networkType +
-                        " to be " + expectedState.toString());
-                synchronized (cmActivity.connectivityObject) {
-                    try {
-                        cmActivity.connectivityObject.wait(STATE_TRANSITION_SHORT_TIMEOUT);
-                    } catch (InterruptedException e) {
-                        e.printStackTrace();
-                    }
-                    if ((cmActivity.mNetworkInfo.getType() != networkType) ||
-                        (cmActivity.mNetworkInfo.getState() != expectedState)) {
-                        Log.v(LOG_TAG, "network state for " + cmActivity.mNetworkInfo.getType() +
-                                "is: " + cmActivity.mNetworkInfo.getState());
-                        continue;
-                    }
-                    break;
+            }
+            Log.v(LOG_TAG, "Wait for the connectivity state for network: " + networkType +
+                    " to be " + expectedState.toString());
+            synchronized (cmActivity.connectivityObject) {
+                try {
+                    cmActivity.connectivityObject.wait(STATE_TRANSITION_SHORT_TIMEOUT);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
                 }
+                if ((cmActivity.mNetworkInfo.getType() != networkType) ||
+                    (cmActivity.mNetworkInfo.getState() != expectedState)) {
+                    Log.v(LOG_TAG, "network state for " + cmActivity.mNetworkInfo.getType() +
+                            "is: " + cmActivity.mNetworkInfo.getState());
+                    continue;
+                }
+                break;
             }
         }
     }
@@ -123,26 +123,26 @@
     //                      WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN
     private void waitForWifiState(int expectedState, long timeout) {
         long startTime = System.currentTimeMillis();
-        if (cmActivity.mWifiState == expectedState) {
-            return;
-        } else {
-            while (true) {
-                if ((System.currentTimeMillis() - startTime) > timeout) {
+        while (true) {
+            if ((System.currentTimeMillis() - startTime) > timeout) {
+                if (cmActivity.mWifiState != expectedState) {
                     assertFalse("Wait for Wifi state timeout", true);
+                } else {
+                    return;
                 }
-                Log.v(LOG_TAG, "Wait for wifi state to be: " + expectedState);
-                synchronized (cmActivity.wifiObject) {
-                    try {
-                        cmActivity.wifiObject.wait(5*1000);
-                    } catch (InterruptedException e) {
-                        e.printStackTrace();
-                    }
-                    if (cmActivity.mWifiState != expectedState) {
-                        Log.v(LOG_TAG, "Wifi state is: " + cmActivity.mWifiNetworkInfo.getState());
-                        continue;
-                    }
-                    break;
+            }
+            Log.v(LOG_TAG, "Wait for wifi state to be: " + expectedState);
+            synchronized (cmActivity.wifiObject) {
+                try {
+                    cmActivity.wifiObject.wait(5*1000);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
                 }
+                if (cmActivity.mWifiState != expectedState) {
+                    Log.v(LOG_TAG, "Wifi state is: " + cmActivity.mWifiNetworkInfo.getState());
+                    continue;
+                }
+                break;
             }
         }
     }
diff --git a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
index 05f61be..c8d241c 100644
--- a/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
+++ b/docs/html/guide/practices/ui_guidelines/activity_task_design.jd
@@ -256,8 +256,8 @@
   to be able go to a subsequent screen B and then use the BACK key to go
   back to screen A, then the screen A needs to be implemented as an
   activity. The one exception to this rule is if your application 
-  <a href=#taking_over_back_key title="takes control of the BACK key"
-  takes control of the BACK key</a> and manages the navigation itself.
+  <a href="#taking_over_back_key">takes control of the BACK key</a> and manages the navigation
+itself.
 </p>
 
 
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
index fd0fc1f..32c9a1d5 100644
--- a/include/binder/Parcel.h
+++ b/include/binder/Parcel.h
@@ -26,11 +26,12 @@
 // ---------------------------------------------------------------------------
 namespace android {
 
+class Flattenable;
 class IBinder;
+class IPCThreadState;
 class ProcessState;
 class String8;
 class TextOutput;
-class Flattenable;
 
 struct flat_binder_object;  // defined in support_p/binder_module.h
 
@@ -61,10 +62,13 @@
 
     // Parses the RPC header, returning true if the interface name
     // in the header matches the expected interface from the caller.
-    // If strict_policy_out is non-NULL, the RPC header's StrictMode policy
-    // mask is returned.
+    //
+    // Additionally, enforceInterface does part of the work of
+    // propagating the StrictMode policy mask, populating the current
+    // IPCThreadState, which as an optimization may optionally be
+    // passed in.
     bool                enforceInterface(const String16& interface,
-                                         int32_t* strict_policy_out = NULL) const;
+                                         IPCThreadState* threadState = NULL) const;
     bool                checkInterface(IBinder*) const;
 
     void                freeData();
diff --git a/include/media/AudioEffect.h b/include/media/AudioEffect.h
index e9ff8a3..c967efb 100644
--- a/include/media/AudioEffect.h
+++ b/include/media/AudioEffect.h
@@ -382,10 +382,10 @@
       *     See EffectApi.h for details on effect command() function, valid command codes
       *     and formats.
       */
-     virtual status_t command(int32_t cmdCode,
-                              int32_t cmdSize,
+     virtual status_t command(uint32_t cmdCode,
+                              uint32_t cmdSize,
                               void *cmdData,
-                              int32_t *replySize,
+                              uint32_t *replySize,
                               void *replyData);
 
 
@@ -429,10 +429,10 @@
         virtual void enableStatusChanged(bool enabled) {
             mEffect->enableStatusChanged(enabled);
         }
-        virtual void commandExecuted(int cmdCode,
-                                     int cmdSize,
+        virtual void commandExecuted(uint32_t cmdCode,
+                                     uint32_t cmdSize,
                                      void *pCmdData,
-                                     int replySize,
+                                     uint32_t replySize,
                                      void *pReplyData) {
             mEffect->commandExecuted(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
         }
@@ -450,7 +450,11 @@
     // IEffectClient
     void controlStatusChanged(bool controlGranted);
     void enableStatusChanged(bool enabled);
-    void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData);
+    void commandExecuted(uint32_t cmdCode,
+                         uint32_t cmdSize,
+                         void *pCmdData,
+                         uint32_t replySize,
+                         void *pReplyData);
     void binderDied();
 
 
diff --git a/include/media/EffectApi.h b/include/media/EffectApi.h
index 8c120e5..16fb43c 100644
--- a/include/media/EffectApi.h
+++ b/include/media/EffectApi.h
@@ -284,10 +284,10 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 typedef int32_t (*effect_command_t)(effect_interface_t self,
-                                    int32_t cmdCode,
-                                    int32_t cmdSize,
+                                    uint32_t cmdCode,
+                                    uint32_t cmdSize,
                                     void *pCmdData,
-                                    int32_t *replySize,
+                                    uint32_t *replySize,
                                     void *pReplyData);
 
 
diff --git a/include/media/IEffect.h b/include/media/IEffect.h
index 6dad393..ff04869 100644
--- a/include/media/IEffect.h
+++ b/include/media/IEffect.h
@@ -33,7 +33,11 @@
 
     virtual status_t disable() = 0;
 
-    virtual status_t command(int cmdCode, int cmdSize, void *pCmdData, int *pReplySize, void *pReplyData) = 0;
+    virtual status_t command(uint32_t cmdCode,
+                             uint32_t cmdSize,
+                             void *pCmdData,
+                             uint32_t *pReplySize,
+                             void *pReplyData) = 0;
 
     virtual void disconnect() = 0;
 
diff --git a/include/media/IEffectClient.h b/include/media/IEffectClient.h
index d22daf8..2f78c98 100644
--- a/include/media/IEffectClient.h
+++ b/include/media/IEffectClient.h
@@ -31,7 +31,11 @@
 
     virtual void controlStatusChanged(bool controlGranted) = 0;
     virtual void enableStatusChanged(bool enabled) = 0;
-    virtual void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData) = 0;
+    virtual void commandExecuted(uint32_t cmdCode,
+                                 uint32_t cmdSize,
+                                 void *pCmdData,
+                                 uint32_t replySize,
+                                 void *pReplyData) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index 5be17d3..dab35b3 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -60,6 +60,31 @@
 class KeyLayoutMap;
 
 /*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+    nsecs_t when;
+    int32_t deviceId;
+    int32_t type;
+    int32_t scanCode;
+    int32_t keyCode;
+    int32_t value;
+    uint32_t flags;
+};
+
+/* Describes an absolute axis. */
+struct RawAbsoluteAxisInfo {
+    bool valid; // true if the information is valid, false otherwise
+
+    int32_t minValue;  // minimum value
+    int32_t maxValue;  // maximum value
+    int32_t flat;      // center flat position, eg. flat == 8 means center is between -8 and 8
+    int32_t fuzz;      // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+
+    inline int32_t getRange() { return maxValue - minValue; }
+};
+
+/*
  * Input device classes.
  */
 enum {
@@ -82,7 +107,10 @@
     INPUT_DEVICE_CLASS_DPAD          = 0x00000020,
 
     /* The input device is a gamepad (implies keyboard). */
-    INPUT_DEVICE_CLASS_GAMEPAD       = 0x00000040
+    INPUT_DEVICE_CLASS_GAMEPAD       = 0x00000040,
+
+    /* The input device has switches. */
+    INPUT_DEVICE_CLASS_SWITCH        = 0x00000080,
 };
 
 /*
@@ -114,8 +142,8 @@
 
     virtual String8 getDeviceName(int32_t deviceId) const = 0;
 
-    virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue,
-            int* outMaxValue, int* outFlat, int* outFuzz) const = 0;
+    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+            RawAbsoluteAxisInfo* outAxisInfo) const = 0;
 
     virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
             int32_t* outKeycode, uint32_t* outFlags) const = 0;
@@ -131,26 +159,19 @@
      * If the device needs to remain awake longer than that, then the caller is responsible
      * for taking care of it (say, by poking the power manager user activity timer).
      */
-    virtual bool getEvent(int32_t* outDeviceId, int32_t* outType,
-            int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
-            int32_t* outValue, nsecs_t* outWhen) = 0;
+    virtual bool getEvent(RawEvent* outEvent) = 0;
 
     /*
      * Query current input state.
-     *   deviceId may be -1 to search for the device automatically, filtered by class.
-     *   deviceClasses may be -1 to ignore device class while searching.
      */
-    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t scanCode) const = 0;
-    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t keyCode) const = 0;
-    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
-            int32_t sw) const = 0;
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
 
     /*
      * Examine key input devices for specific framework keycode support
      */
-    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes,
+    virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
             uint8_t* outFlags) const = 0;
 };
 
@@ -165,33 +186,28 @@
     
     virtual String8 getDeviceName(int32_t deviceId) const;
     
-    virtual int getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue,
-            int* outMaxValue, int* outFlat, int* outFuzz) const;
-        
+    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+            RawAbsoluteAxisInfo* outAxisInfo) const;
+
     virtual status_t scancodeToKeycode(int32_t deviceId, int scancode,
             int32_t* outKeycode, uint32_t* outFlags) const;
 
     virtual void addExcludedDevice(const char* deviceName);
 
-    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t scanCode) const;
-    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t keyCode) const;
-    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
-            int32_t sw) const;
+    virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const;
+    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const;
+    virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const;
 
-    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const;
+    virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags) const;
 
-    virtual bool getEvent(int32_t* outDeviceId, int32_t* outType,
-            int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
-            int32_t* outValue, nsecs_t* outWhen);
+    virtual bool getEvent(RawEvent* outEvent);
 
 protected:
     virtual ~EventHub();
     
 private:
     bool openPlatformInput(void);
-    int32_t convertDeviceKey_TI_P2(int code);
 
     int open_device(const char *device);
     int close_device(const char *device);
@@ -220,6 +236,8 @@
     int32_t getScanCodeStateLocked(device_t* device, int32_t scanCode) const;
     int32_t getKeyCodeStateLocked(device_t* device, int32_t keyCode) const;
     int32_t getSwitchStateLocked(device_t* device, int32_t sw) const;
+    bool markSupportedKeyCodesLocked(device_t* device, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags) const;
 
     // Protect all internal state.
     mutable Mutex   mLock;
diff --git a/include/ui/Input.h b/include/ui/Input.h
index d9b1091..2385973 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -23,7 +23,10 @@
 
 #include <android/input.h>
 #include <utils/Vector.h>
+#include <utils/KeyedVector.h>
 #include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
 
 /*
  * Additional private constants not defined in ndk/ui/input.h.
@@ -47,21 +50,16 @@
     virtual ~AInputEvent() { }
 };
 
-namespace android {
-
 /*
- * A raw event as retrieved from the EventHub.
+ * Declare a concrete type for the NDK's input device forward declaration.
  */
-struct RawEvent {
-    nsecs_t when;
-    int32_t deviceId;
-    int32_t type;
-    int32_t scanCode;
-    int32_t keyCode;
-    int32_t value;
-    uint32_t flags;
+struct AInputDevice {
+    virtual ~AInputDevice() { }
 };
 
+
+namespace android {
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
@@ -424,6 +422,69 @@
     MotionEvent mMotionEvent;
 };
 
+/*
+ * Describes the characteristics and capabilities of an input device.
+ */
+class InputDeviceInfo {
+public:
+    InputDeviceInfo();
+    InputDeviceInfo(const InputDeviceInfo& other);
+    ~InputDeviceInfo();
+
+    struct MotionRange {
+        float min;
+        float max;
+        float flat;
+        float fuzz;
+    };
+
+    void initialize(int32_t id, const String8& name);
+
+    inline int32_t getId() const { return mId; }
+    inline const String8 getName() const { return mName; }
+    inline uint32_t getSources() const { return mSources; }
+
+    const MotionRange* getMotionRange(int32_t rangeType) const;
+
+    void addSource(uint32_t source);
+    void addMotionRange(int32_t rangeType, float min, float max, float flat, float fuzz);
+    void addMotionRange(int32_t rangeType, const MotionRange& range);
+
+    inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
+    inline int32_t getKeyboardType() const { return mKeyboardType; }
+
+private:
+    int32_t mId;
+    String8 mName;
+    uint32_t mSources;
+    int32_t mKeyboardType;
+
+    KeyedVector<int32_t, MotionRange> mMotionRanges;
+};
+
+/*
+ * Provides remote access to information about an input device.
+ *
+ * Note: This is essentially a wrapper for Binder calls into the Window Manager Service.
+ */
+class InputDeviceProxy : public RefBase, public AInputDevice {
+protected:
+    InputDeviceProxy();
+    virtual ~InputDeviceProxy();
+
+public:
+    static void getDeviceIds(Vector<int32_t>& outIds);
+
+    static sp<InputDeviceProxy> getDevice(int32_t id);
+
+    inline const InputDeviceInfo* getInfo() { return & mInfo; }
+
+    // TODO add hasKeys, keymap, etc...
+
+private:
+    InputDeviceInfo mInfo;
+};
+
 
 } // namespace android
 
diff --git a/include/ui/InputDevice.h b/include/ui/InputDevice.h
deleted file mode 100644
index 3b9c70e..0000000
--- a/include/ui/InputDevice.h
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _UI_INPUT_DEVICE_H
-#define _UI_INPUT_DEVICE_H
-
-#include <ui/EventHub.h>
-#include <ui/Input.h>
-#include <utils/KeyedVector.h>
-#include <utils/threads.h>
-#include <utils/Timers.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-#include <utils/BitSet.h>
-
-#include <stddef.h>
-#include <unistd.h>
-
-/* Maximum pointer id value supported.
- * (This is limited by our use of BitSet32 to track pointer assignments.) */
-#define MAX_POINTER_ID 31
-
-/* Maximum number of historical samples to average. */
-#define AVERAGING_HISTORY_SIZE 5
-
-
-namespace android {
-
-extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
-extern int32_t rotateKeyCode(int32_t keyCode, int32_t orientation);
-
-
-/*
- * An input device structure tracks the state of a single input device.
- *
- * This structure is only used by ReaderThread and is not intended to be shared with
- * DispatcherThread (because that would require locking).  This works out fine because
- * DispatcherThread is only interested in cooked event data anyways and does not need
- * any of the low-level data from InputDevice.
- */
-struct InputDevice {
-    struct AbsoluteAxisInfo {
-        bool valid;        // set to true if axis parameters are known, false otherwise
-
-        int32_t minValue;  // minimum value
-        int32_t maxValue;  // maximum value
-        int32_t range;     // range of values, equal to maxValue - minValue
-        int32_t flat;      // center flat position, eg. flat == 8 means center is between -8 and 8
-        int32_t fuzz;      // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
-    };
-
-    struct VirtualKey {
-        int32_t keyCode;
-        int32_t scanCode;
-        uint32_t flags;
-
-        // computed hit box, specified in touch screen coords based on known display size
-        int32_t hitLeft;
-        int32_t hitTop;
-        int32_t hitRight;
-        int32_t hitBottom;
-
-        inline bool isHit(int32_t x, int32_t y) const {
-            return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
-        }
-    };
-
-    struct KeyboardState {
-        struct Current {
-            int32_t metaState;
-            nsecs_t downTime; // time of most recent key down
-        } current;
-
-        void reset();
-    };
-
-    struct TrackballState {
-        struct Accumulator {
-            enum {
-                FIELD_BTN_MOUSE = 1,
-                FIELD_REL_X = 2,
-                FIELD_REL_Y = 4
-            };
-
-            uint32_t fields;
-
-            bool btnMouse;
-            int32_t relX;
-            int32_t relY;
-
-            inline void clear() {
-                fields = 0;
-            }
-
-            inline bool isDirty() {
-                return fields != 0;
-            }
-        } accumulator;
-
-        struct Current {
-            bool down;
-            nsecs_t downTime;
-        } current;
-
-        struct Precalculated {
-            float xScale;
-            float yScale;
-            float xPrecision;
-            float yPrecision;
-        } precalculated;
-
-        void reset();
-    };
-
-    struct SingleTouchScreenState {
-        struct Accumulator {
-            enum {
-                FIELD_BTN_TOUCH = 1,
-                FIELD_ABS_X = 2,
-                FIELD_ABS_Y = 4,
-                FIELD_ABS_PRESSURE = 8,
-                FIELD_ABS_TOOL_WIDTH = 16
-            };
-
-            uint32_t fields;
-
-            bool btnTouch;
-            int32_t absX;
-            int32_t absY;
-            int32_t absPressure;
-            int32_t absToolWidth;
-
-            inline void clear() {
-                fields = 0;
-            }
-
-            inline bool isDirty() {
-                return fields != 0;
-            }
-        } accumulator;
-
-        struct Current {
-            bool down;
-            int32_t x;
-            int32_t y;
-            int32_t pressure;
-            int32_t size;
-        } current;
-
-        void reset();
-    };
-
-    struct MultiTouchScreenState {
-        struct Accumulator {
-            enum {
-                FIELD_ABS_MT_POSITION_X = 1,
-                FIELD_ABS_MT_POSITION_Y = 2,
-                FIELD_ABS_MT_TOUCH_MAJOR = 4,
-                FIELD_ABS_MT_TOUCH_MINOR = 8,
-                FIELD_ABS_MT_WIDTH_MAJOR = 16,
-                FIELD_ABS_MT_WIDTH_MINOR = 32,
-                FIELD_ABS_MT_ORIENTATION = 64,
-                FIELD_ABS_MT_TRACKING_ID = 128
-            };
-
-            uint32_t pointerCount;
-            struct Pointer {
-                uint32_t fields;
-
-                int32_t absMTPositionX;
-                int32_t absMTPositionY;
-                int32_t absMTTouchMajor;
-                int32_t absMTTouchMinor;
-                int32_t absMTWidthMajor;
-                int32_t absMTWidthMinor;
-                int32_t absMTOrientation;
-                int32_t absMTTrackingId;
-
-                inline void clear() {
-                    fields = 0;
-                }
-            } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
-
-            inline void clear() {
-                pointerCount = 0;
-                pointers[0].clear();
-            }
-
-            inline bool isDirty() {
-                return pointerCount != 0;
-            }
-        } accumulator;
-
-        void reset();
-    };
-
-    struct PointerData {
-        uint32_t id;
-        int32_t x;
-        int32_t y;
-        int32_t pressure;
-        int32_t size;
-        int32_t touchMajor;
-        int32_t touchMinor;
-        int32_t toolMajor;
-        int32_t toolMinor;
-        int32_t orientation;
-    };
-
-    struct TouchData {
-        uint32_t pointerCount;
-        PointerData pointers[MAX_POINTERS];
-        BitSet32 idBits;
-        uint32_t idToIndex[MAX_POINTER_ID + 1];
-
-        void copyFrom(const TouchData& other);
-
-        inline void clear() {
-            pointerCount = 0;
-            idBits.clear();
-        }
-    };
-
-    // common state used for both single-touch and multi-touch screens after the initial
-    // touch decoding has been performed
-    struct TouchScreenState {
-        Vector<VirtualKey> virtualKeys;
-
-        struct Parameters {
-            bool useBadTouchFilter;
-            bool useJumpyTouchFilter;
-            bool useAveragingTouchFilter;
-
-            AbsoluteAxisInfo xAxis;
-            AbsoluteAxisInfo yAxis;
-            AbsoluteAxisInfo pressureAxis;
-            AbsoluteAxisInfo sizeAxis;
-            AbsoluteAxisInfo orientationAxis;
-        } parameters;
-
-        // The touch data of the current sample being processed.
-        TouchData currentTouch;
-
-        // The touch data of the previous sample that was processed.  This is updated
-        // incrementally while the current sample is being processed.
-        TouchData lastTouch;
-
-        // The time the primary pointer last went down.
-        nsecs_t downTime;
-
-        struct CurrentVirtualKeyState {
-            enum Status {
-                STATUS_UP,
-                STATUS_DOWN,
-                STATUS_CANCELED
-            };
-
-            Status status;
-            nsecs_t downTime;
-            int32_t keyCode;
-            int32_t scanCode;
-        } currentVirtualKey;
-
-        struct AveragingTouchFilterState {
-            // Individual history tracks are stored by pointer id
-            uint32_t historyStart[MAX_POINTERS];
-            uint32_t historyEnd[MAX_POINTERS];
-            struct {
-                struct {
-                    int32_t x;
-                    int32_t y;
-                    int32_t pressure;
-                } pointers[MAX_POINTERS];
-            } historyData[AVERAGING_HISTORY_SIZE];
-        } averagingTouchFilter;
-
-        struct JumpTouchFilterState {
-            int32_t jumpyPointsDropped;
-        } jumpyTouchFilter;
-
-        struct Precalculated {
-            int32_t xOrigin;
-            float xScale;
-
-            int32_t yOrigin;
-            float yScale;
-
-            int32_t pressureOrigin;
-            float pressureScale;
-
-            int32_t sizeOrigin;
-            float sizeScale;
-
-            float orientationScale;
-        } precalculated;
-
-        void reset();
-
-        bool applyBadTouchFilter();
-        bool applyJumpyTouchFilter();
-        void applyAveragingTouchFilter();
-        void calculatePointerIds();
-
-        bool isPointInsideDisplay(int32_t x, int32_t y) const;
-        const InputDevice::VirtualKey* findVirtualKeyHit() const;
-    };
-
-    InputDevice(int32_t id, uint32_t classes, String8 name);
-
-    int32_t id;
-    uint32_t classes;
-    String8 name;
-    bool ignored;
-
-    KeyboardState keyboard;
-    TrackballState trackball;
-    TouchScreenState touchScreen;
-    union {
-        SingleTouchScreenState singleTouchScreen;
-        MultiTouchScreenState multiTouchScreen;
-    };
-
-    void reset();
-
-    inline bool isKeyboard() const { return classes & INPUT_DEVICE_CLASS_KEYBOARD; }
-    inline bool isAlphaKey() const { return classes & INPUT_DEVICE_CLASS_ALPHAKEY; }
-    inline bool isTrackball() const { return classes & INPUT_DEVICE_CLASS_TRACKBALL; }
-    inline bool isDPad() const { return classes & INPUT_DEVICE_CLASS_DPAD; }
-    inline bool isSingleTouchScreen() const { return (classes
-            & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT))
-            == INPUT_DEVICE_CLASS_TOUCHSCREEN; }
-    inline bool isMultiTouchScreen() const { return classes
-            & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT; }
-    inline bool isTouchScreen() const { return classes
-            & (INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT); }
-};
-
-} // namespace android
-
-#endif // _UI_INPUT_DEVICE_H
diff --git a/include/ui/InputManager.h b/include/ui/InputManager.h
index e755238..7ebec10 100644
--- a/include/ui/InputManager.h
+++ b/include/ui/InputManager.h
@@ -96,22 +96,28 @@
     virtual void preemptInputDispatch() = 0;
 
     /* Gets input device configuration. */
-    virtual void getInputConfiguration(InputConfiguration* outConfiguration) const = 0;
+    virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
 
-    /*
-     * Queries current input state.
-     *   deviceId may be -1 to search for the device automatically, filtered by class.
-     *   deviceClasses may be -1 to ignore device class while searching.
+    /* Gets information about the specified input device.
+     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
+     * was no such device.
      */
-    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t scanCode) const = 0;
-    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t keyCode) const = 0;
-    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
-            int32_t sw) const = 0;
+    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
+
+    /* Gets the list of all registered device ids. */
+    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+
+    /* Queries current input state. */
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t scanCode) = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t keyCode) = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+            int32_t sw) = 0;
 
     /* Determines whether physical keys exist for the given framework-domain key codes. */
-    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0;
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
 };
 
 class InputManager : public InputManagerInterface {
@@ -140,14 +146,17 @@
 
     virtual void preemptInputDispatch();
 
-    virtual void getInputConfiguration(InputConfiguration* outConfiguration) const;
-    virtual int32_t getScanCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t scanCode) const;
-    virtual int32_t getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t keyCode) const;
-    virtual int32_t getSwitchState(int32_t deviceId, int32_t deviceClasses,
-            int32_t sw) const;
-    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const;
+    virtual void getInputConfiguration(InputConfiguration* outConfiguration);
+    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
+    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t scanCode);
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t keyCode);
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+            int32_t sw);
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
 
 private:
     sp<InputReaderInterface> mReader;
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index 14bea65..d7ec8ea 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -19,7 +19,6 @@
 
 #include <ui/EventHub.h>
 #include <ui/Input.h>
-#include <ui/InputDevice.h>
 #include <ui/InputDispatcher.h>
 #include <utils/KeyedVector.h>
 #include <utils/threads.h>
@@ -33,6 +32,10 @@
 
 namespace android {
 
+class InputDevice;
+class InputMapper;
+
+
 /*
  * Input reader policy interface.
  *
@@ -68,14 +71,6 @@
         // The input dispatcher should perform special filtering in preparation for
         // a pending app switch.
         ACTION_APP_SWITCH_COMING = 0x00000002,
-
-        // The input dispatcher should add POLICY_FLAG_WOKE_HERE to the policy flags it
-        // passes through the dispatch pipeline.
-        ACTION_WOKE_HERE = 0x00000004,
-
-        // The input dispatcher should add POLICY_FLAG_BRIGHT_HERE to the policy flags it
-        // passes through the dispatch pipeline.
-        ACTION_BRIGHT_HERE = 0x00000008,
     };
 
     /* Describes a virtual key. */
@@ -101,38 +96,30 @@
 
     /* Intercepts a key event.
      * The policy can use this method as an opportunity to perform power management functions
-     * and early event preprocessing.
+     * and early event preprocessing such as updating policy flags.
      *
      * Returns a policy action constant such as ACTION_DISPATCH.
      */
     virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
-            bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) = 0;
-
-    /* Intercepts a trackball event.
-     * The policy can use this method as an opportunity to perform power management functions
-     * and early event preprocessing.
-     *
-     * Returns a policy action constant such as ACTION_DISPATCH.
-     */
-    virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown,
-            bool rolled) = 0;
-
-    /* Intercepts a touch event.
-     * The policy can use this method as an opportunity to perform power management functions
-     * and early event preprocessing.
-     *
-     * Returns a policy action constant such as ACTION_DISPATCH.
-     */
-    virtual int32_t interceptTouch(nsecs_t when) = 0;
+            bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) = 0;
 
     /* Intercepts a switch event.
      * The policy can use this method as an opportunity to perform power management functions
-     * and early event preprocessing.
+     * and early event preprocessing such as updating policy flags.
      *
      * Switches are not dispatched to applications so this method should
      * usually return ACTION_NONE.
      */
-    virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) = 0;
+    virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+            uint32_t& policyFlags) = 0;
+
+    /* Intercepts a generic touch, trackball or other event.
+     * The policy can use this method as an opportunity to perform power management functions
+     * and early event preprocessing such as updating policy flags.
+     *
+     * Returns a policy action constant such as ACTION_DISPATCH.
+     */
+    virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags) = 0;
 
     /* Determines whether to turn on some hacks we have to improve the touch interaction with a
      * certain device whose screen currently is not all that good.
@@ -167,32 +154,52 @@
      */
     virtual void loopOnce() = 0;
 
-    /* Gets the current virtual key.  Returns false if not down.
-     *
-     * This method may be called on any thread (usually by the input manager).
-     */
-    virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const = 0;
-
     /* Gets the current input device configuration.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual void getCurrentInputConfiguration(InputConfiguration* outConfiguration) const = 0;
+    virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
 
-    /*
-     * Query current input state.
-     *   deviceId may be -1 to search for the device automatically, filtered by class.
-     *   deviceClasses may be -1 to ignore device class while searching.
+    /* Gets information about the specified input device.
+     * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
+     * was no such device.
+     *
+     * This method may be called on any thread (usually by the input manager).
      */
-    virtual int32_t getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t scanCode) const = 0;
-    virtual int32_t getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t keyCode) const = 0;
-    virtual int32_t getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses,
-            int32_t sw) const = 0;
+    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
+
+    /* Gets the list of all registered device ids. */
+    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+
+    /* Query current input state. */
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t scanCode) = 0;
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t keyCode) = 0;
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+            int32_t sw) = 0;
 
     /* Determine whether physical keys exist for the given framework-domain key codes. */
-    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const = 0;
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+};
+
+
+/* Internal interface used by individual input devices to access global input device state
+ * and parameters maintained by the input reader.
+ */
+class InputReaderContext {
+protected:
+    InputReaderContext() { }
+    virtual ~InputReaderContext() { }
+
+public:
+    virtual void updateGlobalMetaState() = 0;
+    virtual int32_t getGlobalMetaState() = 0;
+
+    virtual InputReaderPolicyInterface* getPolicy() = 0;
+    virtual InputDispatcherInterface* getDispatcher() = 0;
+    virtual EventHubInterface* getEventHub() = 0;
 };
 
 
@@ -201,10 +208,11 @@
  * event filtering in low power states, are controlled by a separate policy object.
  *
  * IMPORTANT INVARIANT:
- *     Because the policy can potentially block or cause re-entrance into the input reader,
- *     the input reader never calls into the policy while holding its internal locks.
+ *     Because the policy and dispatcher can potentially block or cause re-entrance into
+ *     the input reader, the input reader never calls into other components while holding
+ *     an exclusive internal lock.
  */
-class InputReader : public InputReaderInterface {
+class InputReader : public InputReaderInterface, private InputReaderContext {
 public:
     InputReader(const sp<EventHubInterface>& eventHub,
             const sp<InputReaderPolicyInterface>& policy,
@@ -213,107 +221,69 @@
 
     virtual void loopOnce();
 
-    virtual bool getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const;
+    virtual void getInputConfiguration(InputConfiguration* outConfiguration);
 
-    virtual void getCurrentInputConfiguration(InputConfiguration* outConfiguration) const;
+    virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
+    virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
 
-    virtual int32_t getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t scanCode) const;
-    virtual int32_t getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-            int32_t keyCode) const;
-    virtual int32_t getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses,
-            int32_t sw) const;
+    virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t scanCode);
+    virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+            int32_t keyCode);
+    virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+            int32_t sw);
 
-    virtual bool hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const;
+    virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+            size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
 
 private:
-    // Lock that must be acquired while manipulating state that may be concurrently accessed
-    // from other threads by input state query methods.  It should be held for as short a
-    // time as possible.
-    //
-    // Exported state:
-    //   - global virtual key code and scan code
-    //   - device list and immutable properties of devices such as id, name, and class
-    //     (but not other internal device state)
-    mutable Mutex mExportedStateLock;
-
-    // current virtual key information (lock mExportedStateLock)
-    int32_t mExportedVirtualKeyCode;
-    int32_t mExportedVirtualScanCode;
-
-    // current input configuration (lock mExportedStateLock)
-    InputConfiguration mExportedInputConfiguration;
-
-    // combined key meta state
-    int32_t mGlobalMetaState;
-
     sp<EventHubInterface> mEventHub;
     sp<InputReaderPolicyInterface> mPolicy;
     sp<InputDispatcherInterface> mDispatcher;
 
+    virtual InputReaderPolicyInterface* getPolicy() { return mPolicy.get(); }
+    virtual InputDispatcherInterface* getDispatcher() { return mDispatcher.get(); }
+    virtual EventHubInterface* getEventHub() { return mEventHub.get(); }
+
+    // This reader/writer lock guards the list of input devices.
+    // The writer lock must be held whenever the list of input devices is modified
+    //   and then promptly released.
+    // The reader lock must be held whenever the list of input devices is traversed or an
+    //   input device in the list is accessed.
+    // This lock only protects the registry and prevents inadvertent deletion of device objects
+    // that are in use.  Individual devices are responsible for guarding their own internal state
+    // as needed for concurrent operation.
+    RWLock mDeviceRegistryLock;
     KeyedVector<int32_t, InputDevice*> mDevices;
 
-    // display properties needed to translate touch screen coordinates into display coordinates
-    int32_t mDisplayOrientation;
-    int32_t mDisplayWidth;
-    int32_t mDisplayHeight;
-
-    // low-level input event decoding
+    // low-level input event decoding and device management
     void process(const RawEvent* rawEvent);
-    void handleDeviceAdded(const RawEvent* rawEvent);
-    void handleDeviceRemoved(const RawEvent* rawEvent);
-    void handleSync(const RawEvent* rawEvent);
-    void handleKey(const RawEvent* rawEvent);
-    void handleRelativeMotion(const RawEvent* rawEvent);
-    void handleAbsoluteMotion(const RawEvent* rawEvent);
-    void handleSwitch(const RawEvent* rawEvent);
 
-    // input policy processing and dispatch
-    void onKey(nsecs_t when, InputDevice* device, bool down,
-            int32_t keyCode, int32_t scanCode, uint32_t policyFlags);
-    void onSwitch(nsecs_t when, InputDevice* device, int32_t switchCode, int32_t switchValue);
-    void onSingleTouchScreenStateChanged(nsecs_t when, InputDevice* device);
-    void onMultiTouchScreenStateChanged(nsecs_t when, InputDevice* device);
-    void onTouchScreenChanged(nsecs_t when, InputDevice* device, bool havePointerIds);
-    void onTrackballStateChanged(nsecs_t when, InputDevice* device);
-    void onConfigurationChanged(nsecs_t when);
-
-    bool applyStandardInputDispatchPolicyActions(nsecs_t when,
-            int32_t policyActions, uint32_t* policyFlags);
-
-    bool consumeVirtualKeyTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags);
-    void dispatchVirtualKey(nsecs_t when, InputDevice* device, uint32_t policyFlags,
-            int32_t keyEventAction, int32_t keyEventFlags);
-    void dispatchTouches(nsecs_t when, InputDevice* device, uint32_t policyFlags);
-    void dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags,
-            InputDevice::TouchData* touch, BitSet32 idBits, uint32_t changedId,
-            int32_t motionEventAction);
-
-    // display
-    void resetDisplayProperties();
-    bool refreshDisplayProperties();
-
-    // device management
-    InputDevice* getDevice(int32_t deviceId);
-    InputDevice* getNonIgnoredDevice(int32_t deviceId);
     void addDevice(nsecs_t when, int32_t deviceId);
-    void removeDevice(nsecs_t when, InputDevice* device);
-    void configureDevice(InputDevice* device);
-    void configureDeviceForCurrentDisplaySize(InputDevice* device);
-    void configureVirtualKeys(InputDevice* device);
-    void configureAbsoluteAxisInfo(InputDevice* device, int axis, const char* name,
-            InputDevice::AbsoluteAxisInfo* out);
+    void removeDevice(nsecs_t when, int32_t deviceId);
+    InputDevice* createDevice(int32_t deviceId, const String8& name, uint32_t classes);
     void configureExcludedDevices();
 
-    // global meta state management for all devices
-    void resetGlobalMetaState();
-    int32_t globalMetaState();
+    void consumeEvent(const RawEvent* rawEvent);
 
-    // virtual key management
-    void updateExportedVirtualKeyState();
+    void handleConfigurationChanged(nsecs_t when);
 
-    // input configuration management
-    void updateExportedInputConfiguration();
+    // state management for all devices
+    Mutex mStateLock;
+
+    int32_t mGlobalMetaState;
+    virtual void updateGlobalMetaState();
+    virtual int32_t getGlobalMetaState();
+
+    InputConfiguration mInputConfiguration;
+    void updateInputConfiguration();
+
+    // state queries
+    typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+    int32_t getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
+            GetStateFunc getStateFunc);
+    bool markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags);
 };
 
 
@@ -329,6 +299,527 @@
     virtual bool threadLoop();
 };
 
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+    InputDevice(InputReaderContext* context, int32_t id, const String8& name);
+    ~InputDevice();
+
+    inline InputReaderContext* getContext() { return mContext; }
+    inline int32_t getId() { return mId; }
+    inline const String8& getName() { return mName; }
+    inline uint32_t getSources() { return mSources; }
+
+    inline bool isIgnored() { return mMappers.isEmpty(); }
+
+    void addMapper(InputMapper* mapper);
+    void configure();
+    void reset();
+    void process(const RawEvent* rawEvent);
+
+    void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
+    int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags);
+
+    int32_t getMetaState();
+
+private:
+    InputReaderContext* mContext;
+    int32_t mId;
+
+    Vector<InputMapper*> mMappers;
+
+    String8 mName;
+    uint32_t mSources;
+
+    typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+    int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+};
+
+
+/* An input mapper transforms raw input events into cooked event data.
+ * A single input device can have multiple associated input mappers in order to interpret
+ * different classes of events.
+ */
+class InputMapper {
+public:
+    InputMapper(InputDevice* device);
+    virtual ~InputMapper();
+
+    inline InputDevice* getDevice() { return mDevice; }
+    inline int32_t getDeviceId() { return mDevice->getId(); }
+    inline const String8 getDeviceName() { return mDevice->getName(); }
+    inline InputReaderContext* getContext() { return mContext; }
+    inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+    inline InputDispatcherInterface* getDispatcher() { return mContext->getDispatcher(); }
+    inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+    virtual uint32_t getSources() = 0;
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void configure();
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent) = 0;
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags);
+
+    virtual int32_t getMetaState();
+
+protected:
+    InputDevice* mDevice;
+    InputReaderContext* mContext;
+
+    bool applyStandardPolicyActions(nsecs_t when, int32_t policyActions);
+};
+
+
+class SwitchInputMapper : public InputMapper {
+public:
+    SwitchInputMapper(InputDevice* device);
+    virtual ~SwitchInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+
+private:
+    void processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue);
+};
+
+
+class KeyboardInputMapper : public InputMapper {
+public:
+    KeyboardInputMapper(InputDevice* device, int32_t associatedDisplayId, uint32_t sources,
+            int32_t keyboardType);
+    virtual ~KeyboardInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags);
+
+    virtual int32_t getMetaState();
+
+private:
+    struct KeyDown {
+        int32_t keyCode;
+        int32_t scanCode;
+    };
+
+    int32_t mAssociatedDisplayId;
+    uint32_t mSources;
+    int32_t mKeyboardType;
+
+    Vector<KeyDown> mKeyDowns; // keys that are down
+    int32_t mMetaState;
+    nsecs_t mDownTime; // time of most recent key down
+
+    void initialize();
+
+    bool isKeyboardOrGamepadKey(int32_t scanCode);
+    void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+            uint32_t policyFlags);
+
+    ssize_t findKeyDown(int32_t scanCode);
+};
+
+
+class TrackballInputMapper : public InputMapper {
+public:
+    TrackballInputMapper(InputDevice* device, int32_t associatedDisplayId);
+    virtual ~TrackballInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+private:
+    // Amount that trackball needs to move in order to generate a key event.
+    static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+    int32_t mAssociatedDisplayId;
+
+    struct Accumulator {
+        enum {
+            FIELD_BTN_MOUSE = 1,
+            FIELD_REL_X = 2,
+            FIELD_REL_Y = 4
+        };
+
+        uint32_t fields;
+
+        bool btnMouse;
+        int32_t relX;
+        int32_t relY;
+
+        inline void clear() {
+            fields = 0;
+        }
+
+        inline bool isDirty() {
+            return fields != 0;
+        }
+    } mAccumulator;
+
+    bool mDown;
+    nsecs_t mDownTime;
+
+    float mXScale;
+    float mYScale;
+    float mXPrecision;
+    float mYPrecision;
+
+    void initialize();
+
+    void sync(nsecs_t when);
+};
+
+
+class TouchInputMapper : public InputMapper {
+public:
+    TouchInputMapper(InputDevice* device, int32_t associatedDisplayId);
+    virtual ~TouchInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void configure();
+    virtual void reset();
+
+    virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+    virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+            const int32_t* keyCodes, uint8_t* outFlags);
+
+protected:
+    /* Maximum pointer id value supported.
+     * (This is limited by our use of BitSet32 to track pointer assignments.) */
+    static const uint32_t MAX_POINTER_ID = 31;
+
+    struct VirtualKey {
+        int32_t keyCode;
+        int32_t scanCode;
+        uint32_t flags;
+
+        // computed hit box, specified in touch screen coords based on known display size
+        int32_t hitLeft;
+        int32_t hitTop;
+        int32_t hitRight;
+        int32_t hitBottom;
+
+        inline bool isHit(int32_t x, int32_t y) const {
+            return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+        }
+    };
+
+    struct PointerData {
+        uint32_t id;
+        int32_t x;
+        int32_t y;
+        int32_t pressure;
+        int32_t size;
+        int32_t touchMajor;
+        int32_t touchMinor;
+        int32_t toolMajor;
+        int32_t toolMinor;
+        int32_t orientation;
+    };
+
+    struct TouchData {
+        uint32_t pointerCount;
+        PointerData pointers[MAX_POINTERS];
+        BitSet32 idBits;
+        uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+        void copyFrom(const TouchData& other) {
+            pointerCount = other.pointerCount;
+            idBits = other.idBits;
+
+            for (uint32_t i = 0; i < pointerCount; i++) {
+                pointers[i] = other.pointers[i];
+                idToIndex[i] = other.idToIndex[i];
+            }
+        }
+
+        inline void clear() {
+            pointerCount = 0;
+            idBits.clear();
+        }
+    };
+
+    int32_t mAssociatedDisplayId;
+    Vector<VirtualKey> mVirtualKeys;
+
+    // Immutable configuration parameters.
+    struct Parameters {
+        bool useBadTouchFilter;
+        bool useJumpyTouchFilter;
+        bool useAveragingTouchFilter;
+    } mParameters;
+
+    // Raw axis information.
+    struct Axes {
+        RawAbsoluteAxisInfo x;
+        RawAbsoluteAxisInfo y;
+        RawAbsoluteAxisInfo pressure;
+        RawAbsoluteAxisInfo size;
+        RawAbsoluteAxisInfo touchMajor;
+        RawAbsoluteAxisInfo touchMinor;
+        RawAbsoluteAxisInfo toolMajor;
+        RawAbsoluteAxisInfo toolMinor;
+        RawAbsoluteAxisInfo orientation;
+    } mAxes;
+
+    // The surface orientation and width and height set by configureSurface().
+    int32_t mSurfaceOrientation;
+    int32_t mSurfaceWidth, mSurfaceHeight;
+
+    // Translation and scaling factors, orientation-independent.
+    int32_t mXOrigin;
+    float mXScale;
+    float mXPrecision;
+
+    int32_t mYOrigin;
+    float mYScale;
+    float mYPrecision;
+
+    int32_t mPressureOrigin;
+    float mPressureScale;
+
+    int32_t mSizeOrigin;
+    float mSizeScale;
+
+    float mOrientationScale;
+
+    // Oriented motion ranges for input device info.
+    struct OrientedRanges {
+        InputDeviceInfo::MotionRange x;
+        InputDeviceInfo::MotionRange y;
+        InputDeviceInfo::MotionRange pressure;
+        InputDeviceInfo::MotionRange size;
+        InputDeviceInfo::MotionRange touchMajor;
+        InputDeviceInfo::MotionRange touchMinor;
+        InputDeviceInfo::MotionRange toolMajor;
+        InputDeviceInfo::MotionRange toolMinor;
+        InputDeviceInfo::MotionRange orientation;
+    } mOrientedRanges;
+
+    // Oriented dimensions and precision.
+    float mOrientedSurfaceWidth, mOrientedSurfaceHeight;
+    float mOrientedXPrecision, mOrientedYPrecision;
+
+    // The touch data of the current sample being processed.
+    TouchData mCurrentTouch;
+
+    // The touch data of the previous sample that was processed.  This is updated
+    // incrementally while the current sample is being processed.
+    TouchData mLastTouch;
+
+    // The time the primary pointer last went down.
+    nsecs_t mDownTime;
+
+    struct CurrentVirtualKeyState {
+        bool down;
+        nsecs_t downTime;
+        int32_t keyCode;
+        int32_t scanCode;
+    } mCurrentVirtualKey;
+
+    // Lock for virtual key state.
+    Mutex mVirtualKeyLock; // methods use "Lvk" suffix
+
+    virtual void configureAxes();
+    virtual bool configureSurface();
+    virtual void configureVirtualKeys();
+
+    enum TouchResult {
+        // Dispatch the touch normally.
+        DISPATCH_TOUCH,
+        // Do not dispatch the touch, but keep tracking the current stroke.
+        SKIP_TOUCH,
+        // Do not dispatch the touch, and drop all information associated with the current stoke
+        // so the next movement will appear as a new down.
+        DROP_STROKE
+    };
+
+    void syncTouch(nsecs_t when, bool havePointerIds);
+
+private:
+    /* Maximum number of historical samples to average. */
+    static const uint32_t AVERAGING_HISTORY_SIZE = 5;
+
+    /* Slop distance for jumpy pointer detection.
+     * The vertical range of the screen divided by this is our epsilon value. */
+    static const uint32_t JUMPY_EPSILON_DIVISOR = 212;
+
+    /* Number of jumpy points to drop for touchscreens that need it. */
+    static const uint32_t JUMPY_TRANSITION_DROPS = 3;
+    static const uint32_t JUMPY_DROP_LIMIT = 3;
+
+    /* Maximum squared distance for averaging.
+     * If moving farther than this, turn of averaging to avoid lag in response. */
+    static const uint64_t AVERAGING_DISTANCE_LIMIT = 75 * 75;
+
+    struct AveragingTouchFilterState {
+        // Individual history tracks are stored by pointer id
+        uint32_t historyStart[MAX_POINTERS];
+        uint32_t historyEnd[MAX_POINTERS];
+        struct {
+            struct {
+                int32_t x;
+                int32_t y;
+                int32_t pressure;
+            } pointers[MAX_POINTERS];
+        } historyData[AVERAGING_HISTORY_SIZE];
+    } mAveragingTouchFilter;
+
+    struct JumpTouchFilterState {
+        uint32_t jumpyPointsDropped;
+    } mJumpyTouchFilter;
+
+    struct PointerDistanceHeapElement {
+        uint32_t currentPointerIndex : 8;
+        uint32_t lastPointerIndex : 8;
+        uint64_t distance : 48; // squared distance
+    };
+
+    void initialize();
+
+    TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
+    void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+    void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch,
+            BitSet32 idBits, uint32_t changedId, int32_t motionEventAction);
+
+    bool isPointInsideSurface(int32_t x, int32_t y);
+    const VirtualKey* findVirtualKeyHitLvk(int32_t x, int32_t y);
+
+    bool applyBadTouchFilter();
+    bool applyJumpyTouchFilter();
+    void applyAveragingTouchFilter();
+    void calculatePointerIds();
+};
+
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+    SingleTouchInputMapper(InputDevice* device, int32_t associatedDisplayId);
+    virtual ~SingleTouchInputMapper();
+
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+protected:
+    virtual void configureAxes();
+
+private:
+    struct Accumulator {
+        enum {
+            FIELD_BTN_TOUCH = 1,
+            FIELD_ABS_X = 2,
+            FIELD_ABS_Y = 4,
+            FIELD_ABS_PRESSURE = 8,
+            FIELD_ABS_TOOL_WIDTH = 16
+        };
+
+        uint32_t fields;
+
+        bool btnTouch;
+        int32_t absX;
+        int32_t absY;
+        int32_t absPressure;
+        int32_t absToolWidth;
+
+        inline void clear() {
+            fields = 0;
+        }
+
+        inline bool isDirty() {
+            return fields != 0;
+        }
+    } mAccumulator;
+
+    bool mDown;
+    int32_t mX;
+    int32_t mY;
+    int32_t mPressure;
+    int32_t mSize;
+
+    void initialize();
+
+    void sync(nsecs_t when);
+};
+
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+    MultiTouchInputMapper(InputDevice* device, int32_t associatedDisplayId);
+    virtual ~MultiTouchInputMapper();
+
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+protected:
+    virtual void configureAxes();
+
+private:
+    struct Accumulator {
+        enum {
+            FIELD_ABS_MT_POSITION_X = 1,
+            FIELD_ABS_MT_POSITION_Y = 2,
+            FIELD_ABS_MT_TOUCH_MAJOR = 4,
+            FIELD_ABS_MT_TOUCH_MINOR = 8,
+            FIELD_ABS_MT_WIDTH_MAJOR = 16,
+            FIELD_ABS_MT_WIDTH_MINOR = 32,
+            FIELD_ABS_MT_ORIENTATION = 64,
+            FIELD_ABS_MT_TRACKING_ID = 128
+        };
+
+        uint32_t pointerCount;
+        struct Pointer {
+            uint32_t fields;
+
+            int32_t absMTPositionX;
+            int32_t absMTPositionY;
+            int32_t absMTTouchMajor;
+            int32_t absMTTouchMinor;
+            int32_t absMTWidthMajor;
+            int32_t absMTWidthMinor;
+            int32_t absMTOrientation;
+            int32_t absMTTrackingId;
+
+            inline void clear() {
+                fields = 0;
+            }
+        } pointers[MAX_POINTERS + 1]; // + 1 to remove the need for extra range checks
+
+        inline void clear() {
+            pointerCount = 0;
+            pointers[0].clear();
+        }
+
+        inline bool isDirty() {
+            return pointerCount != 0;
+        }
+    } mAccumulator;
+
+    void initialize();
+
+    void sync(nsecs_t when);
+};
+
 } // namespace android
 
 #endif // _UI_INPUT_READER_H
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 60babad..18f75df 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -458,13 +458,13 @@
 }
 
 bool Parcel::enforceInterface(const String16& interface,
-                              int32_t* strict_policy_out) const
+                              IPCThreadState* threadState) const
 {
-    int32_t strict_policy = readInt32();
-    IPCThreadState::self()->setStrictModePolicy(strict_policy);
-    if (strict_policy_out != NULL) {
-      *strict_policy_out = strict_policy;
+    int32_t strictPolicy = readInt32();
+    if (threadState == NULL) {
+        threadState = IPCThreadState::self();
     }
+    threadState->setStrictModePolicy(strictPolicy);
     const String16 str(readString16());
     if (str == interface) {
         return true;
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 172952a..a9714c7 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -10,6 +10,7 @@
 	Patch.cpp \
 	PatchCache.cpp \
 	Program.cpp \
+	ProgramCache.cpp \
 	TextureCache.cpp
 
 LOCAL_C_INCLUDES += \
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 2e44e122..0ed6276 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -45,32 +45,22 @@
 #define MB(s) s * 1024 * 1024
 
 // Generates simple and textured vertices
-#define SV(x, y) { { x, y } }
 #define FV(x, y, u, v) { { x, y }, { u, v } }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Globals
 ///////////////////////////////////////////////////////////////////////////////
 
-static const SimpleVertex gDrawColorVertices[] = {
-        SV(0.0f, 0.0f),
-        SV(1.0f, 0.0f),
-        SV(0.0f, 1.0f),
-        SV(1.0f, 1.0f)
-};
-static const GLsizei gDrawColorVertexStride = sizeof(SimpleVertex);
-static const GLsizei gDrawColorVertexCount = 4;
-
 // This array is never used directly but used as a memcpy source in the
 // OpenGLRenderer constructor
-static const TextureVertex gDrawTextureVertices[] = {
+static const TextureVertex gMeshVertices[] = {
         FV(0.0f, 0.0f, 0.0f, 0.0f),
         FV(1.0f, 0.0f, 1.0f, 0.0f),
         FV(0.0f, 1.0f, 0.0f, 1.0f),
         FV(1.0f, 1.0f, 1.0f, 1.0f)
 };
-static const GLsizei gDrawTextureVertexStride = sizeof(TextureVertex);
-static const GLsizei gDrawTextureVertexCount = 4;
+static const GLsizei gMeshStride = sizeof(TextureVertex);
+static const GLsizei gMeshCount = 4;
 
 // In this array, the index of each Blender equals the value of the first
 // entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode]
@@ -143,7 +133,18 @@
 
     mLastTexture = 0;
 
-    memcpy(mDrawTextureVertices, gDrawTextureVertices, sizeof(gDrawTextureVertices));
+    memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
+
+    ProgramDescription d;
+    mProgramCache.get(d);
+    d.hasTexture = true;
+    mProgramCache.get(d);
+    d.hasAlpha8Texture = true;
+    d.hasGradient = true;
+    d.hasBitmap = true;
+    d.shadersMode = SkXfermode::kDstOut_Mode;
+    d.colorOp = ProgramDescription::kColorMatrix;
+    mProgramCache.get(d);
 }
 
 OpenGLRenderer::~OpenGLRenderer() {
@@ -650,9 +651,13 @@
     mModelView.scale(right - left, bottom - top, 1.0f);
 
     if (!useProgram(mDrawColorProgram)) {
-        const GLvoid* p = &gDrawColorVertices[0].position[0];
+        const GLvoid* vertices = &mMeshVertices[0].position[0];
+        const GLvoid* texCoords = &mMeshVertices[0].texture[0];
+
         glVertexAttribPointer(mDrawColorProgram->position, 2, GL_FLOAT, GL_FALSE,
-                gDrawColorVertexStride, p);
+                gMeshStride, vertices);
+        glVertexAttribPointer(mDrawColorProgram->texCoords, 2, GL_FLOAT, GL_FALSE,
+                gMeshStride, texCoords);
     }
 
     if (!ignoreTransform) {
@@ -664,7 +669,7 @@
 
     glUniform4f(mDrawColorProgram->color, r, g, b, a);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawColorVertexCount);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
 }
 
 void OpenGLRenderer::drawLinearGradientShader(float left, float top, float right, float bottom,
@@ -717,9 +722,9 @@
             &screenSpace.data[0]);
 
     glVertexAttribPointer(mDrawLinearGradientProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, &mDrawTextureVertices[0].position[0]);
+            gMeshStride, &mMeshVertices[0].position[0]);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawTextureVertexCount);
+    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
 }
 
 void OpenGLRenderer::drawBitmapShader(float left, float top, float right, float bottom,
@@ -757,7 +762,7 @@
     resetDrawTextureTexCoords(u1, v1, u2, v2);
 
     drawTextureMesh(left, top, right, bottom, texture->id, alpha, mode, texture->blend,
-            &mDrawTextureVertices[0].position[0], &mDrawTextureVertices[0].texture[0], NULL);
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], NULL);
 
     resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
 }
@@ -769,13 +774,13 @@
     getAlphaAndMode(paint, &alpha, &mode);
 
     drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, texture->blend,
-            &mDrawTextureVertices[0].position[0], &mDrawTextureVertices[0].texture[0], NULL);
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], NULL);
 }
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
         GLuint texture, float alpha, SkXfermode::Mode mode, bool blend) {
     drawTextureMesh(left, top, right, bottom, texture, alpha, mode, blend,
-            &mDrawTextureVertices[0].position[0], &mDrawTextureVertices[0].texture[0], NULL);
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0], NULL);
 }
 
 void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
@@ -794,12 +799,12 @@
     glUniform4f(mDrawTextureProgram->color, alpha, alpha, alpha, alpha);
 
     glVertexAttribPointer(mDrawTextureProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, vertices);
+            gMeshStride, vertices);
     glVertexAttribPointer(mDrawTextureProgram->texCoords, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, texCoords);
+            gMeshStride, texCoords);
 
     if (!indices) {
-        glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawTextureVertexCount);
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
     } else {
         glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_SHORT, indices);
     }
@@ -842,7 +847,7 @@
 }
 
 void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, float v2) {
-    TextureVertex* v = &mDrawTextureVertices[0];
+    TextureVertex* v = &mMeshVertices[0];
     TextureVertex::setUV(v++, u1, v1);
     TextureVertex::setUV(v++, u2, v1);
     TextureVertex::setUV(v++, u1, v2);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 248c9c3..975be05 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -41,6 +41,7 @@
 #include "PatchCache.h"
 #include "Vertex.h"
 #include "FontRenderer.h"
+#include "ProgramCache.h"
 
 namespace android {
 namespace uirenderer {
@@ -268,7 +269,7 @@
             SkXfermode::Mode mode);
 
     /**
-     * Resets the texture coordinates stored in mDrawTextureVertices. Setting the values
+     * Resets the texture coordinates stored in mMeshVertices. Setting the values
      * back to default is achieved by calling:
      *
      * resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
@@ -337,7 +338,7 @@
     sp<DrawLinearGradientProgram> mDrawLinearGradientProgram;
 
     // Used to draw textured quads
-    TextureVertex mDrawTextureVertices[4];
+    TextureVertex mMeshVertices[4];
 
     // Current texture state
     GLuint mLastTexture;
@@ -372,6 +373,7 @@
     TextureCache mTextureCache;
     LayerCache mLayerCache;
     GradientCache mGradientCache;
+    ProgramCache mProgramCache;
     PatchCache mPatchCache;
 }; // class OpenGLRenderer
 
diff --git a/libs/hwui/Program.cpp b/libs/hwui/Program.cpp
index 6e60808..86fc154 100644
--- a/libs/hwui/Program.cpp
+++ b/libs/hwui/Program.cpp
@@ -27,7 +27,6 @@
 
 #define SHADER_SOURCE(name, source) const char* name = #source
 
-#include "shaders/drawColor.vert"
 #include "shaders/drawColor.frag"
 
 #include "shaders/drawTexture.vert"
@@ -127,7 +126,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 DrawColorProgram::DrawColorProgram():
-        Program(gDrawColorVertexShader, gDrawColorFragmentShader) {
+        Program(gDrawTextureVertexShader, gDrawColorFragmentShader) {
     getAttribsAndUniforms();
 }
 
@@ -138,6 +137,7 @@
 
 void DrawColorProgram::getAttribsAndUniforms() {
     position = addAttrib("position");
+    texCoords = addAttrib("texCoords");
     color = addUniform("color");
     transform = addUniform("transform");
 }
@@ -154,11 +154,13 @@
 void DrawColorProgram::use() {
     Program::use();
     glEnableVertexAttribArray(position);
+    glEnableVertexAttribArray(texCoords);
 }
 
 void DrawColorProgram::remove() {
     Program::remove();
     glDisableVertexAttribArray(position);
+    glDisableVertexAttribArray(texCoords);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -167,26 +169,21 @@
 
 DrawTextureProgram::DrawTextureProgram():
         DrawColorProgram(gDrawTextureVertexShader, gDrawTextureFragmentShader) {
-    texCoords = addAttrib("texCoords");
     sampler = addUniform("sampler");
 }
 
 DrawTextureProgram::DrawTextureProgram(const char* vertex, const char* fragment):
         DrawColorProgram(vertex, fragment) {
-    texCoords = addAttrib("texCoords");
     sampler = addUniform("sampler");
 }
 
 void DrawTextureProgram::use() {
     DrawColorProgram::use();
-    glActiveTexture(GL_TEXTURE0);
     glUniform1i(sampler, 0);
-    glEnableVertexAttribArray(texCoords);
 }
 
 void DrawTextureProgram::remove() {
     DrawColorProgram::remove();
-    glDisableVertexAttribArray(texCoords);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 824aa05..2cdd905 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -53,6 +53,16 @@
     virtual void remove();
 
     /**
+     * Returns the OpenGL name of the specified attribute.
+     */
+    int getAttrib(const char* name);
+
+    /**
+     * Returns the OpenGL name of the specified uniform.
+     */
+    int getUniform(const char* name);
+
+    /**
      * Indicates whether this program is currently in use with
      * the GL context.
      */
@@ -67,10 +77,6 @@
      * @return The OpenGL name of the attribute.
      */
     int addAttrib(const char* name);
-    /**
-     * Returns the OpenGL name of the specified attribute.
-     */
-    int getAttrib(const char* name);
 
     /**
      * Adds a uniform with the specified name.
@@ -78,10 +84,6 @@
      * @return The OpenGL name of the uniform.
      */
     int addUniform(const char* name);
-    /**
-     * Returns the OpenGL name of the specified uniform.
-     */
-    int getUniform(const char* name);
 
 private:
     /**
@@ -145,6 +147,11 @@
     int position;
 
     /**
+     * Name of the texture coordinates attribute.
+     */
+    int texCoords;
+
+    /**
      * Name of the color uniform.
      */
     int color;
@@ -184,11 +191,6 @@
      * Name of the texture sampler uniform.
      */
     int sampler;
-
-    /**
-     * Name of the texture coordinates attribute.
-     */
-    int texCoords;
 };
 
 class DrawTextProgram: public DrawTextureProgram {
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
new file mode 100644
index 0000000..5a89eb6
--- /dev/null
+++ b/libs/hwui/ProgramCache.cpp
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/String8.h>
+
+#include "ProgramCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Vertex shaders snippets
+///////////////////////////////////////////////////////////////////////////////
+
+// TODO: Implement BitmapShader, implement repeat/mirror for npot
+
+const char* gVS_Header_Attributes =
+        "attribute vec4 position;\n";
+const char* gVS_Header_Attributes_TexCoords =
+        "attribute vec2 texCoords;\n";
+const char* gVS_Header_Uniforms =
+        "uniform mat4 transform;\n";
+const char* gVS_Header_Uniforms_HasGradient =
+        "uniform float gradientLength;\n"
+        "uniform vec2 gradient;\n"
+        "uniform vec2 gradientStart;\n"
+        "uniform mat4 screenSpace;\n";
+const char* gVS_Header_Varyings_HasTexture =
+        "varying vec2 outTexCoords;\n";
+const char* gVS_Header_Varyings_HasBitmap =
+        "varying vec2 outBitmapTexCoords;\n";
+const char* gVS_Header_Varyings_HasGradient =
+        "varying float index;\n";
+const char* gVS_Main =
+        "\nvoid main(void) {\n";
+const char* gVS_Main_OutTexCoords =
+        "    outTexCoords = texCoords;\n";
+const char* gVS_Main_OutGradientIndex =
+        "    vec4 location = screenSpace * position;\n"
+        "    index = dot(location.xy - gradientStart, gradient) * gradientLength;\n";
+const char* gVS_Main_Position =
+        "    gl_Position = transform * position;\n";
+const char* gVS_Footer =
+        "}\n\n";
+
+///////////////////////////////////////////////////////////////////////////////
+// Fragment shaders snippets
+///////////////////////////////////////////////////////////////////////////////
+
+const char* gFS_Header =
+        "precision mediump float;\n\n";
+const char* gFS_Uniforms_Color =
+        "uniform vec4 color;\n";
+const char* gFS_Uniforms_TextureSampler =
+        "uniform sampler2D sampler;\n";
+const char* gFS_Uniforms_GradientSampler =
+        "uniform sampler2D gradientSampler;\n";
+const char* gFS_Uniforms_BitmapSampler =
+        "uniform sampler2D bitmapSampler;\n";
+const char* gFS_Uniforms_ColorOp[4] = {
+        // None
+        "",
+        // Matrix
+        "uniform mat4 colorMatrix;\n"
+        "uniform vec4 colorMatrixVector;\n",
+        // Lighting
+        "uniform float lightingMul;\n"
+        "uniform float lightingAdd;\n",
+        // PorterDuff
+        "uniform vec4 colorBLend;\n"
+};
+const char* gFS_Main =
+        "\nvoid main(void) {\n"
+        "    vec4 fragColor;\n";
+const char* gFS_Main_FetchColor =
+        "    fragColor = color;\n";
+const char* gFS_Main_FetchTexture =
+        "    fragColor = color * texture2D(sampler, outTexCoords);\n";
+const char* gFS_Main_FetchA8Texture =
+        "    fragColor = color * texture2D(sampler, outTexCoords).a;\n";
+const char* gFS_Main_FetchGradient =
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n";
+const char* gFS_Main_FetchBitmap =
+        "    vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
+const char* gFS_Main_BlendShadersBG =
+        "    fragColor = blendShaders(bitmapColor, gradientColor)";
+const char* gFS_Main_BlendShadersGB =
+        "    fragColor = blendShaders(gradientColor, bitmapColor)";
+const char* gFS_Main_BlendShaders_Modulate =
+        " * fragColor.a;\n";
+const char* gFS_Main_FragColor =
+        "    gl_FragColor = fragColor;\n";
+const char* gFS_Main_ApplyColorOp[4] = {
+        // None
+        "",
+        // Matrix
+        "    fragColor *= colorMatrix;\n"
+        "    fragColor += colorMatrixVector;\n",
+        // Lighting
+        "    fragColor *= lightingMul;\n"
+        "    fragColor += lightingAdd;\n",
+        // PorterDuff
+        "    fragColor = blendColors(colorBlend, fragColor);\n"
+};
+const char* gFS_Footer =
+        "}\n\n";
+
+///////////////////////////////////////////////////////////////////////////////
+// PorterDuff snippets
+///////////////////////////////////////////////////////////////////////////////
+
+const char* gPorterDuff[12] = {
+        // Clear
+        "return vec4(0.0, 0.0, 0.0, 0.0);\n",
+        // Src
+        "return src;\n",
+        // Dst
+        "return dst;\n",
+        // SrcOver
+        "return vec4(src.rgb + (1.0 - src.a) * dst.rgb, src.a + dst.a - src.a * dst.a);\n",
+        // DstOver
+        "return vec4(dst.rgb + (1.0 - dst.a) * src.rgb, src.a + dst.a - src.a * dst.a);\n",
+        // SrcIn
+        "return vec4(src.rgb * dst.a, src.a * dst.a);\n",
+        // DstIn
+        "return vec4(dst.rgb * src.a, src.a * dst.a);\n",
+        // SrcOut
+        "return vec4(src.rgb * (1.0 - dst.a), src.a * (1.0 - dst.a));\n",
+        // DstOut
+        "return vec4(dst.rgb * (1.0 - src.a), dst.a * (1.0 - src.a));\n",
+        // SrcAtop
+        "return vec4(src.rgb * dst.a + (1.0 - src.a) * dst.rgb, dst.a);\n",
+        // DstAtop
+        "return vec4(dst.rgb * src.a + (1.0 - dst.a) * src.rgb, src.a);\n",
+        // Xor
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb, "
+                "src.a + dst.a - 2.0 * src.a * dst.a);\n",
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructors
+///////////////////////////////////////////////////////////////////////////////
+
+ProgramCache::ProgramCache() {
+}
+
+ProgramCache::~ProgramCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache management
+///////////////////////////////////////////////////////////////////////////////
+
+void ProgramCache::clear() {
+    size_t count = mCache.size();
+    for (size_t i = 0; i < count; i++) {
+        delete mCache.valueAt(i);
+    }
+    mCache.clear();
+}
+
+Program* ProgramCache::get(const ProgramDescription& description) {
+    programid key = description.key();
+    ssize_t index = mCache.indexOfKey(key);
+    Program* program = NULL;
+    if (index < 0) {
+        PROGRAM_LOGD("Could not find program with key 0x%x", key);
+        program = generateProgram(description, key);
+        mCache.add(key, program);
+    } else {
+        program = mCache.valueAt(index);
+    }
+    return program;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Program generation
+///////////////////////////////////////////////////////////////////////////////
+
+Program* ProgramCache::generateProgram(const ProgramDescription& description, programid key) {
+    String8 vertexShader = generateVertexShader(description);
+    String8 fragmentShader = generateFragmentShader(description);
+
+    Program* program = new Program(vertexShader.string(), fragmentShader.string());
+    return program;
+}
+
+String8 ProgramCache::generateVertexShader(const ProgramDescription& description) {
+    // Add attributes
+    String8 shader(gVS_Header_Attributes);
+    if (description.hasTexture || description.hasBitmap) {
+        shader.append(gVS_Header_Attributes_TexCoords);
+    }
+    // Uniforms
+    shader.append(gVS_Header_Uniforms);
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Uniforms_HasGradient);
+    }
+    // Varyings
+    if (description.hasTexture) {
+        shader.append(gVS_Header_Varyings_HasTexture);
+    }
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Varyings_HasGradient);
+    }
+    if (description.hasBitmap) {
+        shader.append(gVS_Header_Varyings_HasBitmap);
+    }
+
+    // Begin the shader
+    shader.append(gVS_Main); {
+        if (description.hasTexture) {
+            shader.append(gVS_Main_OutTexCoords);
+        }
+        if (description.hasGradient) {
+            shader.append(gVS_Main_OutGradientIndex);
+        }
+        // Output transformed position
+        shader.append(gVS_Main_Position);
+    }
+    // End the shader
+    shader.append(gVS_Footer);
+
+    PROGRAM_LOGD("*** Generated vertex shader:\n\n%s", shader.string());
+
+    return shader;
+}
+
+String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) {
+    // Set the default precision
+    String8 shader(gFS_Header);
+
+    // Varyings
+    if (description.hasTexture) {
+        shader.append(gVS_Header_Varyings_HasTexture);
+    }
+    if (description.hasGradient) {
+        shader.append(gVS_Header_Varyings_HasGradient);
+    }
+    if (description.hasBitmap) {
+        shader.append(gVS_Header_Varyings_HasBitmap);
+    }
+
+
+    // Uniforms
+    shader.append(gFS_Uniforms_Color);
+    if (description.hasTexture) {
+        shader.append(gFS_Uniforms_TextureSampler);
+    }
+    if (description.hasGradient) {
+        shader.append(gFS_Uniforms_GradientSampler);
+    }
+    if (description.hasBitmap) {
+        shader.append(gFS_Uniforms_BitmapSampler);
+    }
+    shader.append(gFS_Uniforms_ColorOp[description.colorOp]);
+
+    // Generate required functions
+    if (description.hasGradient && description.hasBitmap) {
+        generatePorterDuffBlend(shader, "blendShaders", description.shadersMode);
+    }
+    if (description.colorOp == ProgramDescription::kColorBlend) {
+        generatePorterDuffBlend(shader, "blendColors", description.colorMode);
+    }
+
+    // Begin the shader
+    shader.append(gFS_Main); {
+        // Stores the result in fragColor directly
+        if (description.hasTexture) {
+            if (description.hasAlpha8Texture) {
+                shader.append(gFS_Main_FetchA8Texture);
+            } else {
+                shader.append(gFS_Main_FetchTexture);
+            }
+        } else {
+            shader.append(gFS_Main_FetchColor);
+        }
+        if (description.hasGradient) {
+            shader.append(gFS_Main_FetchGradient);
+        }
+        if (description.hasBitmap) {
+            shader.append(gFS_Main_FetchBitmap);
+        }
+        // Case when we have two shaders set
+        if (description.hasGradient && description.hasBitmap) {
+            if (description.isBitmapFirst) {
+                shader.append(gFS_Main_BlendShadersBG);
+            } else {
+                shader.append(gFS_Main_BlendShadersGB);
+            }
+            shader.append(gFS_Main_BlendShaders_Modulate);
+        }
+        // Apply the color op if needed
+        shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
+        // Output the fragment
+        shader.append(gFS_Main_FragColor);
+    }
+    // End the shader
+    shader.append(gFS_Footer);
+
+    PROGRAM_LOGD("*** Generated fragment shader:\n\n%s", shader.string());
+    return shader;
+}
+
+void ProgramCache::generatePorterDuffBlend(String8& shader, const char* name,
+        SkXfermode::Mode mode) {
+    shader.append("\nvec4 ");
+    shader.append(name);
+    shader.append("(vec4 src, vec4 dst) {\n");
+    shader.append("    ");
+    shader.append(gPorterDuff[mode]);
+    shader.append("}\n");
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
new file mode 100644
index 0000000..7f2f5fa
--- /dev/null
+++ b/libs/hwui/ProgramCache.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_PROGRAM_CACHE_H
+#define ANDROID_UI_PROGRAM_CACHE_H
+
+#include <utils/KeyedVector.h>
+#include <utils/Log.h>
+
+#include <SkXfermode.h>
+
+#include "Program.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_PROGRAM_CACHE 0
+
+// Debug
+#if DEBUG_PROGRAM_CACHE
+    #define PROGRAM_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define PROGRAM_LOGD(...)
+#endif
+
+#define PROGRAM_KEY_TEXTURE 0x1
+#define PROGRAM_KEY_A8_TEXTURE 0x2
+#define PROGRAM_KEY_BITMAP 0x4
+#define PROGRAM_KEY_GRADIENT 0x8
+#define PROGRAM_KEY_BITMAP_FIRST 0x10
+#define PROGRAM_KEY_COLOR_MATRIX 0x20
+#define PROGRAM_KEY_COLOR_LIGHTING 0x40
+#define PROGRAM_KEY_COLOR_BLEND 0x80
+
+// Support only the 12 Porter-Duff modes for now
+#define PROGRAM_MAX_XFERMODE 0xC
+#define PROGRAM_XFERMODE_SHADER_SHIFT 24
+#define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
+
+///////////////////////////////////////////////////////////////////////////////
+// Types
+///////////////////////////////////////////////////////////////////////////////
+
+typedef uint32_t programid;
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Describe the features required for a given program. The features
+ * determine the generation of both the vertex and fragment shaders.
+ * A ProgramDescription must be used in conjunction with a ProgramCache.
+ */
+struct ProgramDescription {
+    enum ColorModifier {
+        kColorNone,
+        kColorMatrix,
+        kColorLighting,
+        kColorBlend
+    };
+
+    ProgramDescription():
+        hasTexture(false), hasAlpha8Texture(false),
+        hasBitmap(false), hasGradient(false), shadersMode(SkXfermode::kClear_Mode),
+        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode) {
+    }
+
+    // Texturing
+    bool hasTexture;
+    bool hasAlpha8Texture;
+
+    // Shaders
+    bool hasBitmap;
+    bool hasGradient;
+    SkXfermode::Mode shadersMode;
+    bool isBitmapFirst;
+
+    // Color operations
+    int colorOp;
+    SkXfermode::Mode colorMode;
+
+    programid key() const {
+        programid key = 0;
+        if (hasTexture) key |= PROGRAM_KEY_TEXTURE;
+        if (hasAlpha8Texture) key |= PROGRAM_KEY_A8_TEXTURE;
+        if (hasBitmap) key |= PROGRAM_KEY_BITMAP;
+        if (hasGradient) key |= PROGRAM_KEY_GRADIENT;
+        if (isBitmapFirst) key  |= PROGRAM_KEY_BITMAP_FIRST;
+        if (hasBitmap && hasGradient) {
+            key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT;
+        }
+        switch (colorOp) {
+            case kColorMatrix:
+                key |= PROGRAM_KEY_COLOR_MATRIX;
+                break;
+            case kColorLighting:
+                key |= PROGRAM_KEY_COLOR_LIGHTING;
+                break;
+            case kColorBlend:
+                key |= PROGRAM_KEY_COLOR_BLEND;
+                key |= (colorMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_COLOR_OP_SHIFT;
+                break;
+            case kColorNone:
+                break;
+        }
+        return key;
+    }
+}; // struct ProgramDescription
+
+/**
+ * Generates and caches program. Programs are generated based on
+ * ProgramDescriptions.
+ */
+class ProgramCache {
+public:
+    ProgramCache();
+    ~ProgramCache();
+
+    Program* get(const ProgramDescription& description);
+
+    void clear();
+
+private:
+    Program* generateProgram(const ProgramDescription& description, programid key);
+    String8 generateVertexShader(const ProgramDescription& description);
+    String8 generateFragmentShader(const ProgramDescription& description);
+    void generatePorterDuffBlend(String8& shader, const char* name, SkXfermode::Mode mode);
+
+    KeyedVector<programid, Program*> mCache;
+
+}; // class ProgramCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PROGRAM_CACHE_H
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index ffd0633..1f54086 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -21,14 +21,6 @@
 namespace uirenderer {
 
 /**
- * Simple structure to describe a vertex with a position.
- * This is used to draw filled rectangles without a texture.
- */
-struct SimpleVertex {
-    float position[2];
-}; // struct SimpleVertex
-
-/**
  * Simple structure to describe a vertex with a position and a texture.
  */
 struct TextureVertex {
diff --git a/libs/hwui/shaders/drawColor.vert b/libs/hwui/shaders/drawColor.vert
deleted file mode 100644
index 20e2636..0000000
--- a/libs/hwui/shaders/drawColor.vert
+++ /dev/null
@@ -1,11 +0,0 @@
-SHADER_SOURCE(gDrawColorVertexShader,
-
-attribute vec4 position;
-
-uniform mat4 transform;
-
-void main(void) {
-    gl_Position = transform * position;
-}
-
-);
diff --git a/libs/hwui/shaders/drawLinearGradient.vert b/libs/hwui/shaders/drawLinearGradient.vert
index f5c669b..d2f857d 100644
--- a/libs/hwui/shaders/drawLinearGradient.vert
+++ b/libs/hwui/shaders/drawLinearGradient.vert
@@ -2,10 +2,10 @@
 
 attribute vec4 position;
 
+uniform mat4 transform;
 uniform float gradientLength;
 uniform vec2 gradient;
 uniform vec2 start;
-uniform mat4 transform;
 uniform mat4 screenSpace;
 
 varying float index;
diff --git a/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs b/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
index 34bea2f..f87ef59 100644
--- a/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
+++ b/libs/rs/java/Fountain/src/com/android/fountain/fountain.rs
@@ -19,13 +19,14 @@
 #pragma rs export_func(addParticles)
 
 int root() {
+    float dt = min(rsGetDt(), 0.1f);
     rsgClearColor(0.f, 0.f, 0.f, 1.f);
     const float height = rsgGetHeight();
     const int size = rsAllocationGetDimX(rsGetAllocation(point));
-
+    float dy2 = dt * (10.f);
     Point_t * p = point;
     for (int ct=0; ct < size; ct++) {
-        p->delta.y += 0.15f;
+        p->delta.y += dy2;
         p->position += p->delta;
         if ((p->position.y > height) && (p->delta.y > 0)) {
             p->delta.y *= -0.3f;
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
index 5dcd372..8e198c7 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/threshold.rs
@@ -17,6 +17,7 @@
 rs_script hBlurScript;
 rs_script levelsScript;
 
+const int CMD_FINISHED = 1;
 
 // Store our coefficients here
 static float gaussian[MAX_RADIUS * 2 + 1];
@@ -89,14 +90,11 @@
         rsForEach(levelsScript, rsGetAllocation(InPixel), rsGetAllocation(OutPixel), 0);
     }
 
-    int count = 0;
-    rsSendToClient(&count, 1, 4, 0);
+    rsSendToClientBlocking(CMD_FINISHED);
 }
 
 void filterBenchmark() {
     blur();
-
-    int count = 0;
-    rsSendToClient(&count, 1, 4, 0);
+    rsSendToClientBlocking(CMD_FINISHED);
 }
 
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index d6df581..d1784f3 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -780,9 +780,13 @@
         }
     }
     //LOGE("sendMessageToClient 2");
-    void *p = mIO.mToClient.reserve(len);
-    memcpy(p, data, len);
-    mIO.mToClient.commit(cmdID, len);
+    if (len > 0) {
+        void *p = mIO.mToClient.reserve(len);
+        memcpy(p, data, len);
+        mIO.mToClient.commit(cmdID, len);
+    } else {
+        mIO.mToClient.commit(cmdID, 0);
+    }
     //LOGE("sendMessageToClient 3");
     return true;
 }
diff --git a/libs/rs/rsScript.h b/libs/rs/rsScript.h
index 0717059..455ece7 100644
--- a/libs/rs/rsScript.h
+++ b/libs/rs/rsScript.h
@@ -40,7 +40,8 @@
     virtual ~Script();
 
     struct Enviroment_t {
-        uint32_t mStartTimeMillis;
+        int64_t mStartTimeMillis;
+        int64_t mLastDtTime;
         const char* mTimeZone;
 
         ObjectBaseRef<ProgramVertex> mVertex;
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index 9c29ca6..ccdde00 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -179,112 +179,30 @@
     return timeinfo->tm_year;
 }
 
-static int64_t SC_uptimeMillis2()
+static int64_t SC_uptimeMillis()
 {
     return nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
 }
 
-static int64_t SC_startTimeMillis2()
+static int64_t SC_startTimeMillis()
 {
     GET_TLS();
     return sc->mEnviroment.mStartTimeMillis;
 }
 
-static int64_t SC_elapsedTimeMillis2()
+static int64_t SC_elapsedTimeMillis()
 {
     GET_TLS();
     return nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC))
             - sc->mEnviroment.mStartTimeMillis;
 }
 
-static int32_t SC_uptimeMillis()
-{
-    return nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC));
-}
-
-static int32_t SC_startTimeMillis()
+static float SC_getDt()
 {
     GET_TLS();
-    return sc->mEnviroment.mStartTimeMillis;
-}
-
-static int32_t SC_elapsedTimeMillis()
-{
-    GET_TLS();
-    return nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_MONOTONIC))
-            - sc->mEnviroment.mStartTimeMillis;
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Matrix routines
-//////////////////////////////////////////////////////////////////////////////
-
-
-static void SC_matrixLoadIdentity(rsc_Matrix *mat)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadIdentity();
-}
-
-static void SC_matrixLoadFloat(rsc_Matrix *mat, const float *f)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->load(f);
-}
-
-static void SC_matrixLoadMat(rsc_Matrix *mat, const rsc_Matrix *newmat)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->load(reinterpret_cast<const Matrix *>(newmat));
-}
-
-static void SC_matrixLoadRotate(rsc_Matrix *mat, float rot, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadRotate(rot, x, y, z);
-}
-
-static void SC_matrixLoadScale(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadScale(x, y, z);
-}
-
-static void SC_matrixLoadTranslate(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadTranslate(x, y, z);
-}
-
-static void SC_matrixLoadMultiply(rsc_Matrix *mat, const rsc_Matrix *lhs, const rsc_Matrix *rhs)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->loadMultiply(reinterpret_cast<const Matrix *>(lhs),
-                    reinterpret_cast<const Matrix *>(rhs));
-}
-
-static void SC_matrixMultiply(rsc_Matrix *mat, const rsc_Matrix *rhs)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->multiply(reinterpret_cast<const Matrix *>(rhs));
-}
-
-static void SC_matrixRotate(rsc_Matrix *mat, float rot, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->rotate(rot, x, y, z);
-}
-
-static void SC_matrixScale(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->scale(x, y, z);
-}
-
-static void SC_matrixTranslate(rsc_Matrix *mat, float x, float y, float z)
-{
-    Matrix *m = reinterpret_cast<Matrix *>(mat);
-    m->translate(x, y, z);
+    int64_t l = sc->mEnviroment.mLastDtTime;
+    sc->mEnviroment.mLastDtTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    return ((float)(sc->mEnviroment.mLastDtTime - l)) / 1.0e9;
 }
 
 
@@ -372,22 +290,40 @@
 static void SC_debugI32(const char *s, int32_t i) {
     LOGE("%s %i  0x%x", s, i, i);
 }
+static void SC_debugU32(const char *s, uint32_t i) {
+    LOGE("%s %i  0x%x", s, i, i);
+}
 
 static void SC_debugP(const char *s, const void *p) {
     LOGE("%s %p", s, p);
 }
 
-static uint32_t SC_toClient(void *data, int cmdID, int len, int waitForSpace)
+static uint32_t SC_toClient2(int cmdID, void *data, int len)
 {
     GET_TLS();
-    //LOGE("SC_toClient %i %i %i", cmdID, len, waitForSpace);
-    return rsc->sendMessageToClient(data, cmdID, len, waitForSpace != 0);
+    //LOGE("SC_toClient %i %i %i", cmdID, len);
+    return rsc->sendMessageToClient(data, cmdID, len, false);
 }
 
-static void SC_scriptCall(int scriptID)
+static uint32_t SC_toClient(int cmdID)
 {
     GET_TLS();
-    rsc->runScript((Script *)scriptID);
+    //LOGE("SC_toClient %i", cmdID);
+    return rsc->sendMessageToClient(NULL, cmdID, 0, false);
+}
+
+static uint32_t SC_toClientBlocking2(int cmdID, void *data, int len)
+{
+    GET_TLS();
+    //LOGE("SC_toClientBlocking %i %i", cmdID, len);
+    return rsc->sendMessageToClient(data, cmdID, len, true);
+}
+
+static uint32_t SC_toClientBlocking(int cmdID)
+{
+    GET_TLS();
+    //LOGE("SC_toClientBlocking %i", cmdID);
+    return rsc->sendMessageToClient(NULL, cmdID, 0, true);
 }
 
 int SC_divsi3(int a, int b)
@@ -453,12 +389,12 @@
     { "__divsi3", (void *)&SC_divsi3 },
 
     // allocation
-    { "rsAllocationGetDimX", (void *)&SC_allocGetDimX },
-    { "rsAllocationGetDimY", (void *)&SC_allocGetDimY },
-    { "rsAllocationGetDimZ", (void *)&SC_allocGetDimZ },
-    { "rsAllocationGetDimLOD", (void *)&SC_allocGetDimLOD },
-    { "rsAllocationGetDimFaces", (void *)&SC_allocGetDimFaces },
-    { "rsGetAllocation", (void *)&SC_getAllocation },
+    { "_Z19rsAllocationGetDimX13rs_allocation", (void *)&SC_allocGetDimX },
+    { "_Z19rsAllocationGetDimY13rs_allocation", (void *)&SC_allocGetDimY },
+    { "_Z19rsAllocationGetDimZ13rs_allocation", (void *)&SC_allocGetDimZ },
+    { "_Z21rsAllocationGetDimLOD13rs_allocation", (void *)&SC_allocGetDimLOD },
+    { "_Z23rsAllocationGetDimFaces13rs_allocation", (void *)&SC_allocGetDimFaces },
+    { "_Z15rsGetAllocationPKv", (void *)&SC_getAllocation },
 
     { "_Z14rsGetElementAt13rs_allocationj", (void *)&SC_getElementAtX },
     { "_Z14rsGetElementAt13rs_allocationjj", (void *)&SC_getElementAtXY },
@@ -471,6 +407,7 @@
     { "_Z7rsDebugPKcDv3_f", (void *)&SC_debugFv3 },
     { "_Z7rsDebugPKcDv4_f", (void *)&SC_debugFv4 },
     { "_Z7rsDebugPKci", (void *)&SC_debugI32 },
+    { "_Z7rsDebugPKcj", (void *)&SC_debugU32 },
     { "_Z7rsDebugPKcPKv", (void *)&SC_debugP },
     //extern void __attribute__((overloadable))rsDebug(const char *, const void *);
 
@@ -483,30 +420,32 @@
     { "_Z6rsFracf", (void *)&SC_frac },
 
     // time
+    { "_Z8rsSecond", (void *)&SC_second },
+    { "_Z8rsMinute", (void *)&SC_minute },
+    { "_Z6rsHour", (void *)&SC_hour },
+    { "_Z5rsDay", (void *)&SC_day },
+    { "_Z7rsMonth", (void *)&SC_month },
+    { "_Z6rsYear", (void *)&SC_year },
+    { "_Z14rsUptimeMillis", (void*)&SC_uptimeMillis },
+    { "_Z17rsStartTimeMillis", (void*)&SC_startTimeMillis },
+    { "_Z19rsElapsedTimeMillis", (void*)&SC_elapsedTimeMillis },
+    { "_Z7rsGetDt", (void*)&SC_getDt },
+
     { "rsSecond", (void *)&SC_second },
     { "rsMinute", (void *)&SC_minute },
     { "rsHour", (void *)&SC_hour },
     { "rsDay", (void *)&SC_day },
     { "rsMonth", (void *)&SC_month },
     { "rsYear", (void *)&SC_year },
-    { "rsUptimeMillis", (void*)&SC_uptimeMillis2 },
-    { "rsStartTimeMillis", (void*)&SC_startTimeMillis2 },
-    { "rsElapsedTimeMillis", (void*)&SC_elapsedTimeMillis2 },
+    { "rsUptimeMillis", (void*)&SC_uptimeMillis },
+    { "rsStartTimeMillis", (void*)&SC_startTimeMillis },
+    { "rsElapsedTimeMillis", (void*)&SC_elapsedTimeMillis },
+    { "rsGetDt", (void*)&SC_getDt },
 
-    { "rsSendToClient", (void *)&SC_toClient },
-
-    // matrix
-    { "rsMatrixLoadIdentity", (void *)&SC_matrixLoadIdentity },
-    { "rsMatrixLoadFloat", (void *)&SC_matrixLoadFloat },
-    { "rsMatrixLoadMat", (void *)&SC_matrixLoadMat },
-    { "rsMatrixLoadRotate", (void *)&SC_matrixLoadRotate },
-    { "rsMatrixLoadScale", (void *)&SC_matrixLoadScale },
-    { "rsMatrixLoadTranslate", (void *)&SC_matrixLoadTranslate },
-    { "rsMatrixLoadMultiply", (void *)&SC_matrixLoadMultiply },
-    { "rsMatrixMultiply", (void *)&SC_matrixMultiply },
-    { "rsMatrixRotate", (void *)&SC_matrixRotate },
-    { "rsMatrixScale", (void *)&SC_matrixScale },
-    { "rsMatrixTranslate", (void *)&SC_matrixTranslate },
+    { "_Z14rsSendToClienti", (void *)&SC_toClient },
+    { "_Z14rsSendToClientiPKvj", (void *)&SC_toClient2 },
+    { "_Z22rsSendToClientBlockingi", (void *)&SC_toClientBlocking },
+    { "_Z22rsSendToClientBlockingiPKvj", (void *)&SC_toClientBlocking2 },
 
     { "_Z9rsForEach9rs_script13rs_allocationS0_PKv", (void *)&SC_ForEach },
     //{ "_Z9rsForEach9rs_script13rs_allocationS0_PKv", (void *)&SC_ForEach2 },
@@ -516,9 +455,6 @@
     //{ "sinf_fast", (void *)&SC_sinf_fast },
     //{ "cosf_fast", (void *)&SC_cosf_fast },
 
-    { "scriptCall", (void *)&SC_scriptCall },
-
-
     { NULL, NULL }
 };
 
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index bd6e5a9..309a952 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -7,12 +7,18 @@
 
 
 // Allocations
-extern rs_allocation rsGetAllocation(const void *);
-extern uint32_t rsAllocationGetDimX(rs_allocation);
-extern uint32_t rsAllocationGetDimY(rs_allocation);
-extern uint32_t rsAllocationGetDimZ(rs_allocation);
-extern uint32_t rsAllocationGetDimLOD(rs_allocation);
-extern uint32_t rsAllocationGetDimFaces(rs_allocation);
+extern rs_allocation __attribute__((overloadable))
+    rsGetAllocation(const void *);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimX(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimY(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimZ(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimLOD(rs_allocation);
+extern uint32_t __attribute__((overloadable))
+    rsAllocationGetDimFaces(rs_allocation);
 
 extern const void * __attribute__((overloadable))
     rsGetElementAt(rs_allocation, uint32_t x);
@@ -28,30 +34,35 @@
 extern void __attribute__((overloadable))rsDebug(const char *, float3);
 extern void __attribute__((overloadable))rsDebug(const char *, float4);
 extern void __attribute__((overloadable))rsDebug(const char *, int);
+extern void __attribute__((overloadable))rsDebug(const char *, uint);
 extern void __attribute__((overloadable))rsDebug(const char *, const void *);
 #define RS_DEBUG(a) rsDebug(#a, a)
 #define RS_DEBUG_MARKER rsDebug(__FILE__, __LINE__)
 
 // RS Math
-extern int __attribute__((overloadable)) rsRand(int);
-extern int __attribute__((overloadable)) rsRand(int, int);
-extern float __attribute__((overloadable)) rsRand(float);
-extern float __attribute__((overloadable)) rsRand(float, float);
+extern int __attribute__((overloadable))rsRand(int);
+extern int __attribute__((overloadable))rsRand(int, int);
+extern float __attribute__((overloadable))rsRand(float);
+extern float __attribute__((overloadable))rsRand(float, float);
 
 extern float __attribute__((overloadable)) rsFrac(float);
 
 // time
-extern int32_t rsSecond();
-extern int32_t rsMinute();
-extern int32_t rsHour();
-extern int32_t rsDay();
-extern int32_t rsMonth();
-extern int32_t rsYear();
-extern int64_t rsUptimeMillis();
-extern int64_t rsStartTimeMillis();
-extern int64_t rsElapsedTimeMillis();
+extern int32_t /*__attribute__((overloadable))*/rsSecond();
+extern int32_t /*__attribute__((overloadable))*/rsMinute();
+extern int32_t /*__attribute__((overloadable))*/rsHour();
+extern int32_t /*__attribute__((overloadable))*/rsDay();
+extern int32_t /*__attribute__((overloadable))*/rsMonth();
+extern int32_t /*__attribute__((overloadable))*/rsYear();
+extern int64_t /*__attribute__((overloadable))*/rsUptimeMillis();
+extern int64_t /*__attribute__((overloadable))*/rsStartTimeMillis();
+extern int64_t /*__attribute__((overloadable))*/rsElapsedTimeMillis();
+extern float /*__attribute__((overloadable))*/rsGetDt();
 
-extern int rsSendToClient(void *data, int cmdID, int len, int waitForSpace);
+extern bool __attribute__((overloadable))rsSendToClient(int cmdID);
+extern bool __attribute__((overloadable))rsSendToClient(int cmdID, const void *data, uint len);
+extern void __attribute__((overloadable))rsSendToClientBlocking(int cmdID);
+extern void __attribute__((overloadable))rsSendToClientBlocking(int cmdID, const void *data, uint len);
 
 // Script to Script
 typedef struct rs_script_call {
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index 4243bbf..9f49348 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -12,7 +12,6 @@
 	KeyLayoutMap.cpp \
 	KeyCharacterMap.cpp \
 	Input.cpp \
-	InputDevice.cpp \
 	InputDispatcher.cpp \
 	InputManager.cpp \
 	InputReader.cpp \
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 33dd3732..124f7b3 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -137,9 +137,14 @@
     return device->classes;
 }
 
-int EventHub::getAbsoluteInfo(int32_t deviceId, int axis, int *outMinValue,
-        int* outMaxValue, int* outFlat, int* outFuzz) const
-{
+status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+        RawAbsoluteAxisInfo* outAxisInfo) const {
+    outAxisInfo->valid = false;
+    outAxisInfo->minValue = 0;
+    outAxisInfo->maxValue = 0;
+    outAxisInfo->flat = 0;
+    outAxisInfo->fuzz = 0;
+
     AutoMutex _l(mLock);
     device_t* device = getDevice(deviceId);
     if (device == NULL) return -1;
@@ -147,38 +152,28 @@
     struct input_absinfo info;
 
     if(ioctl(mFDs[id_to_index(device->id)].fd, EVIOCGABS(axis), &info)) {
-        LOGE("Error reading absolute controller %d for device %s fd %d\n",
+        LOGW("Error reading absolute controller %d for device %s fd %d\n",
              axis, device->name.string(), mFDs[id_to_index(device->id)].fd);
-        return -1;
+        return -errno;
     }
-    *outMinValue = info.minimum;
-    *outMaxValue = info.maximum;
-    *outFlat = info.flat;
-    *outFuzz = info.fuzz;
-    return 0;
+
+    if (info.minimum != info.maximum) {
+        outAxisInfo->valid = true;
+        outAxisInfo->minValue = info.minimum;
+        outAxisInfo->maxValue = info.maximum;
+        outAxisInfo->flat = info.flat;
+        outAxisInfo->fuzz = info.fuzz;
+    }
+    return OK;
 }
 
-int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t deviceClasses,
-        int32_t scanCode) const {
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
     if (scanCode >= 0 && scanCode <= KEY_MAX) {
         AutoMutex _l(mLock);
 
-        if (deviceId == -1) {
-            for (int i = 0; i < mNumDevicesById; i++) {
-                device_t* device = mDevicesById[i].device;
-                if (device != NULL && (device->classes & deviceClasses) != 0) {
-                    int32_t result = getScanCodeStateLocked(device, scanCode);
-                    if (result >= AKEY_STATE_DOWN) {
-                        return result;
-                    }
-                }
-            }
-            return AKEY_STATE_UP;
-        } else {
-            device_t* device = getDevice(deviceId);
-            if (device != NULL) {
-                return getScanCodeStateLocked(device, scanCode);
-            }
+        device_t* device = getDevice(deviceId);
+        if (device != NULL) {
+            return getScanCodeStateLocked(device, scanCode);
         }
     }
     return AKEY_STATE_UNKNOWN;
@@ -194,25 +189,12 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-        int32_t keyCode) const {
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+    AutoMutex _l(mLock);
 
-    if (deviceId == -1) {
-        for (int i = 0; i < mNumDevicesById; i++) {
-            device_t* device = mDevicesById[i].device;
-            if (device != NULL && (device->classes & deviceClasses) != 0) {
-                int32_t result = getKeyCodeStateLocked(device, keyCode);
-                if (result >= AKEY_STATE_DOWN) {
-                    return result;
-                }
-            }
-        }
-        return AKEY_STATE_UP;
-    } else {
-        device_t* device = getDevice(deviceId);
-        if (device != NULL) {
-            return getKeyCodeStateLocked(device, keyCode);
-        }
+    device_t* device = getDevice(deviceId);
+    if (device != NULL) {
+        return getKeyCodeStateLocked(device, keyCode);
     }
     return AKEY_STATE_UNKNOWN;
 }
@@ -243,24 +225,15 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-int32_t EventHub::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const {
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
 #ifdef EV_SW
     if (sw >= 0 && sw <= SW_MAX) {
         AutoMutex _l(mLock);
 
-        if (deviceId == -1) {
-            deviceId = mSwitches[sw];
-            if (deviceId == 0) {
-                return AKEY_STATE_UNKNOWN;
-            }
-        }
-
         device_t* device = getDevice(deviceId);
-        if (device == NULL) {
-            return AKEY_STATE_UNKNOWN;
+        if (device != NULL) {
+            return getSwitchStateLocked(device, sw);
         }
-
-        return getSwitchStateLocked(device, sw);
     }
 #endif
     return AKEY_STATE_UNKNOWN;
@@ -276,6 +249,42 @@
     return AKEY_STATE_UNKNOWN;
 }
 
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) const {
+    AutoMutex _l(mLock);
+
+    device_t* device = getDevice(deviceId);
+    if (device != NULL) {
+        return markSupportedKeyCodesLocked(device, numCodes, keyCodes, outFlags);
+    }
+    return false;
+}
+
+bool EventHub::markSupportedKeyCodesLocked(device_t* device, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) const {
+    if (device->layoutMap == NULL || device->keyBitmask == NULL) {
+        return false;
+    }
+
+    Vector<int32_t> scanCodes;
+    for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+        scanCodes.clear();
+
+        status_t err = device->layoutMap->findScancodes(keyCodes[codeIndex], &scanCodes);
+        if (! err) {
+            // check the possible scan codes identified by the layout map against the
+            // map of codes actually emitted by the driver
+            for (size_t sc = 0; sc < scanCodes.size(); sc++) {
+                if (test_bit(scanCodes[sc], device->keyBitmask)) {
+                    outFlags[codeIndex] = 1;
+                    break;
+                }
+            }
+        }
+    }
+    return true;
+}
+
 status_t EventHub::scancodeToKeycode(int32_t deviceId, int scancode,
         int32_t* outKeycode, uint32_t* outFlags) const
 {
@@ -324,17 +333,15 @@
     return NULL;
 }
 
-bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
-        int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
-        int32_t* outValue, nsecs_t* outWhen)
+bool EventHub::getEvent(RawEvent* outEvent)
 {
-    *outDeviceId = 0;
-    *outType = 0;
-    *outScancode = 0;
-    *outKeycode = 0;
-    *outFlags = 0;
-    *outValue = 0;
-    *outWhen = 0;
+    outEvent->deviceId = 0;
+    outEvent->type = 0;
+    outEvent->scanCode = 0;
+    outEvent->keyCode = 0;
+    outEvent->flags = 0;
+    outEvent->value = 0;
+    outEvent->when = 0;
 
     status_t err;
 
@@ -359,20 +366,27 @@
             LOGV("Reporting device closed: id=0x%x, name=%s\n",
                  device->id, device->path.string());
             mClosingDevices = device->next;
-            *outDeviceId = device->id;
-            if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
-            *outType = DEVICE_REMOVED;
+            if (device->id == mFirstKeyboardId) {
+                outEvent->deviceId = 0;
+            } else {
+                outEvent->deviceId = device->id;
+            }
+            outEvent->type = DEVICE_REMOVED;
             delete device;
             return true;
         }
+
         if (mOpeningDevices != NULL) {
             device_t* device = mOpeningDevices;
             LOGV("Reporting device opened: id=0x%x, name=%s\n",
                  device->id, device->path.string());
             mOpeningDevices = device->next;
-            *outDeviceId = device->id;
-            if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
-            *outType = DEVICE_ADDED;
+            if (device->id == mFirstKeyboardId) {
+                outEvent->deviceId = 0;
+            } else {
+                outEvent->deviceId = device->id;
+            }
+            outEvent->type = DEVICE_ADDED;
             return true;
         }
 
@@ -399,27 +413,36 @@
                 if(mFDs[i].revents & POLLIN) {
                     res = read(mFDs[i].fd, &iev, sizeof(iev));
                     if (res == sizeof(iev)) {
+                        device_t* device = mDevices[i];
                         LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",
-                             mDevices[i]->path.string(),
+                             device->path.string(),
                              (int) iev.time.tv_sec, (int) iev.time.tv_usec,
                              iev.type, iev.code, iev.value);
-                        *outDeviceId = mDevices[i]->id;
-                        if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
-                        *outType = iev.type;
-                        *outScancode = iev.code;
+                        if (device->id == mFirstKeyboardId) {
+                            outEvent->deviceId = 0;
+                        } else {
+                            outEvent->deviceId = device->id;
+                        }
+                        outEvent->type = iev.type;
+                        outEvent->scanCode = iev.code;
                         if (iev.type == EV_KEY) {
-                            err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
-                            LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d\n",
-                                iev.code, *outKeycode, *outFlags, err);
+                            err = device->layoutMap->map(iev.code,
+                                    & outEvent->keyCode, & outEvent->flags);
+                            LOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
+                                iev.code, outEvent->keyCode, outEvent->flags, err);
                             if (err != 0) {
-                                *outKeycode = AKEYCODE_UNKNOWN;
-                                *outFlags = 0;
+                                outEvent->keyCode = AKEYCODE_UNKNOWN;
+                                outEvent->flags = 0;
                             }
                         } else {
-                            *outKeycode = iev.code;
+                            outEvent->keyCode = iev.code;
                         }
-                        *outValue = iev.value;
-                        *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
+                        outEvent->value = iev.value;
+
+                        // Use an event timestamp in the same timebase as
+                        // java.lang.System.nanoTime() and android.os.SystemClock.uptimeMillis()
+                        // as expected by the rest of the system.
+                        outEvent->when = systemTime(SYSTEM_TIME_MONOTONIC);
                         return true;
                     } else {
                         if (res<0) {
@@ -479,37 +502,6 @@
     return true;
 }
 
-/*
- * Inspect the known devices to determine whether physical keys exist for the given
- * framework-domain key codes.
- */
-bool EventHub::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const {
-    for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
-        outFlags[codeIndex] = 0;
-
-        // check each available hardware device for support for this keycode
-        Vector<int32_t> scanCodes;
-        for (int n = 0; (n < mFDCount) && (outFlags[codeIndex] == 0); n++) {
-            if (mDevices[n]) {
-                status_t err = mDevices[n]->layoutMap->findScancodes(
-                        keyCodes[codeIndex], &scanCodes);
-                if (!err) {
-                    // check the possible scan codes identified by the layout map against the
-                    // map of codes actually emitted by the driver
-                    for (size_t sc = 0; sc < scanCodes.size(); sc++) {
-                        if (test_bit(scanCodes[sc], mDevices[n]->keyBitmask)) {
-                            outFlags[codeIndex] = 1;
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    return true;
-}
-
 // ----------------------------------------------------------------------------
 
 static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) {
@@ -715,16 +707,21 @@
     // figure out the switches this device reports
     uint8_t sw_bitmask[sizeof_bit_array(SW_MAX + 1)];
     memset(sw_bitmask, 0, sizeof(sw_bitmask));
+    bool hasSwitches = false;
     if (ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask) >= 0) {
         for (int i=0; i<EV_SW; i++) {
             //LOGI("Device 0x%x sw %d: has=%d", device->id, i, test_bit(i, sw_bitmask));
             if (test_bit(i, sw_bitmask)) {
+                hasSwitches = true;
                 if (mSwitches[i] == 0) {
                     mSwitches[i] = device->id;
                 }
             }
         }
     }
+    if (hasSwitches) {
+        device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+    }
 #endif
 
     if ((device->classes & INPUT_DEVICE_CLASS_KEYBOARD) != 0) {
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index 5253c72..5fbaf09 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -168,4 +168,63 @@
     mYOffset += yOffset;
 }
 
+// class InputDeviceInfo
+
+InputDeviceInfo::InputDeviceInfo() {
+    initialize(-1, String8("uninitialized device info"));
+}
+
+InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
+        mId(other.mId), mName(other.mName), mSources(other.mSources),
+        mKeyboardType(other.mKeyboardType),
+        mMotionRanges(other.mMotionRanges) {
+}
+
+InputDeviceInfo::~InputDeviceInfo() {
+}
+
+void InputDeviceInfo::initialize(int32_t id, const String8& name) {
+    mId = id;
+    mName = name;
+    mSources = 0;
+    mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+    mMotionRanges.clear();
+}
+
+const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(int32_t rangeType) const {
+    ssize_t index = mMotionRanges.indexOfKey(rangeType);
+    return index >= 0 ? & mMotionRanges.valueAt(index) : NULL;
+}
+
+void InputDeviceInfo::addSource(uint32_t source) {
+    mSources |= source;
+}
+
+void InputDeviceInfo::addMotionRange(int32_t rangeType, float min, float max,
+        float flat, float fuzz) {
+    MotionRange range = { min, max, flat, fuzz };
+    addMotionRange(rangeType, range);
+}
+
+void InputDeviceInfo::addMotionRange(int32_t rangeType, const MotionRange& range) {
+    mMotionRanges.add(rangeType, range);
+}
+
+// class InputDeviceProxy
+
+InputDeviceProxy::InputDeviceProxy() {
+}
+
+InputDeviceProxy::~InputDeviceProxy() {
+}
+
+void InputDeviceProxy::getDeviceIds(Vector<int32_t>& outIds) {
+    // TODO use Binder
+}
+
+sp<InputDeviceProxy> InputDeviceProxy::getDevice(int32_t id) {
+    // TODO use Binder
+    return NULL;
+}
+
 } // namespace android
diff --git a/libs/ui/InputDevice.cpp b/libs/ui/InputDevice.cpp
deleted file mode 100644
index b2a4d6c..0000000
--- a/libs/ui/InputDevice.cpp
+++ /dev/null
@@ -1,729 +0,0 @@
-//
-// Copyright 2010 The Android Open Source Project
-//
-// The input reader.
-//
-#define LOG_TAG "InputDevice"
-
-//#define LOG_NDEBUG 0
-
-// Log debug messages for each raw event received from the EventHub.
-#define DEBUG_RAW_EVENTS 0
-
-// Log debug messages about touch screen filtering hacks.
-#define DEBUG_HACKS 0
-
-// Log debug messages about virtual key processing.
-#define DEBUG_VIRTUAL_KEYS 0
-
-// Log debug messages about pointers.
-#define DEBUG_POINTERS 0
-
-// Log debug messages about pointer assignment calculations.
-#define DEBUG_POINTER_ASSIGNMENT 0
-
-#include <cutils/log.h>
-#include <ui/InputDevice.h>
-
-#include <stddef.h>
-#include <unistd.h>
-#include <errno.h>
-#include <limits.h>
-
-/* Slop distance for jumpy pointer detection.
- * The vertical range of the screen divided by this is our epsilon value. */
-#define JUMPY_EPSILON_DIVISOR 212
-
-/* Number of jumpy points to drop for touchscreens that need it. */
-#define JUMPY_TRANSITION_DROPS 3
-#define JUMPY_DROP_LIMIT 3
-
-/* Maximum squared distance for averaging.
- * If moving farther than this, turn of averaging to avoid lag in response. */
-#define AVERAGING_DISTANCE_LIMIT (75 * 75)
-
-
-namespace android {
-
-// --- Static Functions ---
-
-template<typename T>
-inline static T abs(const T& value) {
-    return value < 0 ? - value : value;
-}
-
-template<typename T>
-inline static T min(const T& a, const T& b) {
-    return a < b ? a : b;
-}
-
-template<typename T>
-inline static void swap(T& a, T& b) {
-    T temp = a;
-    a = b;
-    b = temp;
-}
-
-
-// --- InputDevice ---
-
-InputDevice::InputDevice(int32_t id, uint32_t classes, String8 name) :
-    id(id), classes(classes), name(name), ignored(false) {
-}
-
-void InputDevice::reset() {
-    if (isKeyboard()) {
-        keyboard.reset();
-    }
-
-    if (isTrackball()) {
-        trackball.reset();
-    }
-
-    if (isMultiTouchScreen()) {
-        multiTouchScreen.reset();
-    } else if (isSingleTouchScreen()) {
-        singleTouchScreen.reset();
-    }
-
-    if (isTouchScreen()) {
-        touchScreen.reset();
-    }
-}
-
-
-// --- InputDevice::TouchData ---
-
-void InputDevice::TouchData::copyFrom(const TouchData& other) {
-    pointerCount = other.pointerCount;
-    idBits = other.idBits;
-
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointers[i] = other.pointers[i];
-        idToIndex[i] = other.idToIndex[i];
-    }
-}
-
-
-// --- InputDevice::KeyboardState ---
-
-void InputDevice::KeyboardState::reset() {
-    current.metaState = AMETA_NONE;
-    current.downTime = 0;
-}
-
-
-// --- InputDevice::TrackballState ---
-
-void InputDevice::TrackballState::reset() {
-    accumulator.clear();
-    current.down = false;
-    current.downTime = 0;
-}
-
-
-// --- InputDevice::TouchScreenState ---
-
-void InputDevice::TouchScreenState::reset() {
-    lastTouch.clear();
-    downTime = 0;
-    currentVirtualKey.status = CurrentVirtualKeyState::STATUS_UP;
-
-    for (uint32_t i = 0; i < MAX_POINTERS; i++) {
-        averagingTouchFilter.historyStart[i] = 0;
-        averagingTouchFilter.historyEnd[i] = 0;
-    }
-
-    jumpyTouchFilter.jumpyPointsDropped = 0;
-}
-
-struct PointerDistanceHeapElement {
-    uint32_t currentPointerIndex : 8;
-    uint32_t lastPointerIndex : 8;
-    uint64_t distance : 48; // squared distance
-};
-
-void InputDevice::TouchScreenState::calculatePointerIds() {
-    uint32_t currentPointerCount = currentTouch.pointerCount;
-    uint32_t lastPointerCount = lastTouch.pointerCount;
-
-    if (currentPointerCount == 0) {
-        // No pointers to assign.
-        currentTouch.idBits.clear();
-    } else if (lastPointerCount == 0) {
-        // All pointers are new.
-        currentTouch.idBits.clear();
-        for (uint32_t i = 0; i < currentPointerCount; i++) {
-            currentTouch.pointers[i].id = i;
-            currentTouch.idToIndex[i] = i;
-            currentTouch.idBits.markBit(i);
-        }
-    } else if (currentPointerCount == 1 && lastPointerCount == 1) {
-        // Only one pointer and no change in count so it must have the same id as before.
-        uint32_t id = lastTouch.pointers[0].id;
-        currentTouch.pointers[0].id = id;
-        currentTouch.idToIndex[id] = 0;
-        currentTouch.idBits.value = BitSet32::valueForBit(id);
-    } else {
-        // General case.
-        // We build a heap of squared euclidean distances between current and last pointers
-        // associated with the current and last pointer indices.  Then, we find the best
-        // match (by distance) for each current pointer.
-        PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
-
-        uint32_t heapSize = 0;
-        for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
-                currentPointerIndex++) {
-            for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
-                    lastPointerIndex++) {
-                int64_t deltaX = currentTouch.pointers[currentPointerIndex].x
-                        - lastTouch.pointers[lastPointerIndex].x;
-                int64_t deltaY = currentTouch.pointers[currentPointerIndex].y
-                        - lastTouch.pointers[lastPointerIndex].y;
-
-                uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
-
-                // Insert new element into the heap (sift up).
-                heap[heapSize].currentPointerIndex = currentPointerIndex;
-                heap[heapSize].lastPointerIndex = lastPointerIndex;
-                heap[heapSize].distance = distance;
-                heapSize += 1;
-            }
-        }
-
-        // Heapify
-        for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
-            startIndex -= 1;
-            for (uint32_t parentIndex = startIndex; ;) {
-                uint32_t childIndex = parentIndex * 2 + 1;
-                if (childIndex >= heapSize) {
-                    break;
-                }
-
-                if (childIndex + 1 < heapSize
-                        && heap[childIndex + 1].distance < heap[childIndex].distance) {
-                    childIndex += 1;
-                }
-
-                if (heap[parentIndex].distance <= heap[childIndex].distance) {
-                    break;
-                }
-
-                swap(heap[parentIndex], heap[childIndex]);
-                parentIndex = childIndex;
-            }
-        }
-
-#if DEBUG_POINTER_ASSIGNMENT
-        LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize);
-        for (size_t i = 0; i < heapSize; i++) {
-            LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
-                    i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
-                    heap[i].distance);
-        }
-#endif
-
-        // Pull matches out by increasing order of distance.
-        // To avoid reassigning pointers that have already been matched, the loop keeps track
-        // of which last and current pointers have been matched using the matchedXXXBits variables.
-        // It also tracks the used pointer id bits.
-        BitSet32 matchedLastBits(0);
-        BitSet32 matchedCurrentBits(0);
-        BitSet32 usedIdBits(0);
-        bool first = true;
-        for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) {
-            for (;;) {
-                if (first) {
-                    // The first time through the loop, we just consume the root element of
-                    // the heap (the one with smallest distance).
-                    first = false;
-                } else {
-                    // Previous iterations consumed the root element of the heap.
-                    // Pop root element off of the heap (sift down).
-                    heapSize -= 1;
-                    assert(heapSize > 0);
-
-                    // Sift down.
-                    heap[0] = heap[heapSize];
-                    for (uint32_t parentIndex = 0; ;) {
-                        uint32_t childIndex = parentIndex * 2 + 1;
-                        if (childIndex >= heapSize) {
-                            break;
-                        }
-
-                        if (childIndex + 1 < heapSize
-                                && heap[childIndex + 1].distance < heap[childIndex].distance) {
-                            childIndex += 1;
-                        }
-
-                        if (heap[parentIndex].distance <= heap[childIndex].distance) {
-                            break;
-                        }
-
-                        swap(heap[parentIndex], heap[childIndex]);
-                        parentIndex = childIndex;
-                    }
-
-#if DEBUG_POINTER_ASSIGNMENT
-                    LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize);
-                    for (size_t i = 0; i < heapSize; i++) {
-                        LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
-                                i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
-                                heap[i].distance);
-                    }
-#endif
-                }
-
-                uint32_t currentPointerIndex = heap[0].currentPointerIndex;
-                if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
-
-                uint32_t lastPointerIndex = heap[0].lastPointerIndex;
-                if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
-
-                matchedCurrentBits.markBit(currentPointerIndex);
-                matchedLastBits.markBit(lastPointerIndex);
-
-                uint32_t id = lastTouch.pointers[lastPointerIndex].id;
-                currentTouch.pointers[currentPointerIndex].id = id;
-                currentTouch.idToIndex[id] = currentPointerIndex;
-                usedIdBits.markBit(id);
-
-#if DEBUG_POINTER_ASSIGNMENT
-                LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
-                        lastPointerIndex, currentPointerIndex, id, heap[0].distance);
-#endif
-                break;
-            }
-        }
-
-        // Assign fresh ids to new pointers.
-        if (currentPointerCount > lastPointerCount) {
-            for (uint32_t i = currentPointerCount - lastPointerCount; ;) {
-                uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit();
-                uint32_t id = usedIdBits.firstUnmarkedBit();
-
-                currentTouch.pointers[currentPointerIndex].id = id;
-                currentTouch.idToIndex[id] = currentPointerIndex;
-                usedIdBits.markBit(id);
-
-#if DEBUG_POINTER_ASSIGNMENT
-                LOGD("calculatePointerIds - assigned: cur=%d, id=%d",
-                        currentPointerIndex, id);
-#endif
-
-                if (--i == 0) break; // done
-                matchedCurrentBits.markBit(currentPointerIndex);
-            }
-        }
-
-        // Fix id bits.
-        currentTouch.idBits = usedIdBits;
-    }
-}
-
-/* Special hack for devices that have bad screen data: if one of the
- * points has moved more than a screen height from the last position,
- * then drop it. */
-bool InputDevice::TouchScreenState::applyBadTouchFilter() {
-    // This hack requires valid axis parameters.
-    if (! parameters.yAxis.valid) {
-        return false;
-    }
-
-    uint32_t pointerCount = currentTouch.pointerCount;
-
-    // Nothing to do if there are no points.
-    if (pointerCount == 0) {
-        return false;
-    }
-
-    // Don't do anything if a finger is going down or up.  We run
-    // here before assigning pointer IDs, so there isn't a good
-    // way to do per-finger matching.
-    if (pointerCount != lastTouch.pointerCount) {
-        return false;
-    }
-
-    // We consider a single movement across more than a 7/16 of
-    // the long size of the screen to be bad.  This was a magic value
-    // determined by looking at the maximum distance it is feasible
-    // to actually move in one sample.
-    int32_t maxDeltaY = parameters.yAxis.range * 7 / 16;
-
-    // XXX The original code in InputDevice.java included commented out
-    //     code for testing the X axis.  Note that when we drop a point
-    //     we don't actually restore the old X either.  Strange.
-    //     The old code also tries to track when bad points were previously
-    //     detected but it turns out that due to the placement of a "break"
-    //     at the end of the loop, we never set mDroppedBadPoint to true
-    //     so it is effectively dead code.
-    // Need to figure out if the old code is busted or just overcomplicated
-    // but working as intended.
-
-    // Look through all new points and see if any are farther than
-    // acceptable from all previous points.
-    for (uint32_t i = pointerCount; i-- > 0; ) {
-        int32_t y = currentTouch.pointers[i].y;
-        int32_t closestY = INT_MAX;
-        int32_t closestDeltaY = 0;
-
-#if DEBUG_HACKS
-        LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y);
-#endif
-
-        for (uint32_t j = pointerCount; j-- > 0; ) {
-            int32_t lastY = lastTouch.pointers[j].y;
-            int32_t deltaY = abs(y - lastY);
-
-#if DEBUG_HACKS
-            LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d",
-                    j, lastY, deltaY);
-#endif
-
-            if (deltaY < maxDeltaY) {
-                goto SkipSufficientlyClosePoint;
-            }
-            if (deltaY < closestDeltaY) {
-                closestDeltaY = deltaY;
-                closestY = lastY;
-            }
-        }
-
-        // Must not have found a close enough match.
-#if DEBUG_HACKS
-        LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d",
-                i, y, closestY, closestDeltaY, maxDeltaY);
-#endif
-
-        currentTouch.pointers[i].y = closestY;
-        return true; // XXX original code only corrects one point
-
-    SkipSufficientlyClosePoint: ;
-    }
-
-    // No change.
-    return false;
-}
-
-/* Special hack for devices that have bad screen data: drop points where
- * the coordinate value for one axis has jumped to the other pointer's location.
- */
-bool InputDevice::TouchScreenState::applyJumpyTouchFilter() {
-    // This hack requires valid axis parameters.
-    if (! parameters.yAxis.valid) {
-        return false;
-    }
-
-    uint32_t pointerCount = currentTouch.pointerCount;
-    if (lastTouch.pointerCount != pointerCount) {
-#if DEBUG_HACKS
-        LOGD("JumpyTouchFilter: Different pointer count %d -> %d",
-                lastTouch.pointerCount, pointerCount);
-        for (uint32_t i = 0; i < pointerCount; i++) {
-            LOGD("  Pointer %d (%d, %d)", i,
-                    currentTouch.pointers[i].x, currentTouch.pointers[i].y);
-        }
-#endif
-
-        if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
-            if (lastTouch.pointerCount == 1 && pointerCount == 2) {
-                // Just drop the first few events going from 1 to 2 pointers.
-                // They're bad often enough that they're not worth considering.
-                currentTouch.pointerCount = 1;
-                jumpyTouchFilter.jumpyPointsDropped += 1;
-
-#if DEBUG_HACKS
-                LOGD("JumpyTouchFilter: Pointer 2 dropped");
-#endif
-                return true;
-            } else if (lastTouch.pointerCount == 2 && pointerCount == 1) {
-                // The event when we go from 2 -> 1 tends to be messed up too
-                currentTouch.pointerCount = 2;
-                currentTouch.pointers[0] = lastTouch.pointers[0];
-                currentTouch.pointers[1] = lastTouch.pointers[1];
-                jumpyTouchFilter.jumpyPointsDropped += 1;
-
-#if DEBUG_HACKS
-                for (int32_t i = 0; i < 2; i++) {
-                    LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i,
-                            currentTouch.pointers[i].x, currentTouch.pointers[i].y);
-                }
-#endif
-                return true;
-            }
-        }
-        // Reset jumpy points dropped on other transitions or if limit exceeded.
-        jumpyTouchFilter.jumpyPointsDropped = 0;
-
-#if DEBUG_HACKS
-        LOGD("JumpyTouchFilter: Transition - drop limit reset");
-#endif
-        return false;
-    }
-
-    // We have the same number of pointers as last time.
-    // A 'jumpy' point is one where the coordinate value for one axis
-    // has jumped to the other pointer's location. No need to do anything
-    // else if we only have one pointer.
-    if (pointerCount < 2) {
-        return false;
-    }
-
-    if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
-        int jumpyEpsilon = parameters.yAxis.range / JUMPY_EPSILON_DIVISOR;
-
-        // We only replace the single worst jumpy point as characterized by pointer distance
-        // in a single axis.
-        int32_t badPointerIndex = -1;
-        int32_t badPointerReplacementIndex = -1;
-        int32_t badPointerDistance = INT_MIN; // distance to be corrected
-
-        for (uint32_t i = pointerCount; i-- > 0; ) {
-            int32_t x = currentTouch.pointers[i].x;
-            int32_t y = currentTouch.pointers[i].y;
-
-#if DEBUG_HACKS
-            LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y);
-#endif
-
-            // Check if a touch point is too close to another's coordinates
-            bool dropX = false, dropY = false;
-            for (uint32_t j = 0; j < pointerCount; j++) {
-                if (i == j) {
-                    continue;
-                }
-
-                if (abs(x - currentTouch.pointers[j].x) <= jumpyEpsilon) {
-                    dropX = true;
-                    break;
-                }
-
-                if (abs(y - currentTouch.pointers[j].y) <= jumpyEpsilon) {
-                    dropY = true;
-                    break;
-                }
-            }
-            if (! dropX && ! dropY) {
-                continue; // not jumpy
-            }
-
-            // Find a replacement candidate by comparing with older points on the
-            // complementary (non-jumpy) axis.
-            int32_t distance = INT_MIN; // distance to be corrected
-            int32_t replacementIndex = -1;
-
-            if (dropX) {
-                // X looks too close.  Find an older replacement point with a close Y.
-                int32_t smallestDeltaY = INT_MAX;
-                for (uint32_t j = 0; j < pointerCount; j++) {
-                    int32_t deltaY = abs(y - lastTouch.pointers[j].y);
-                    if (deltaY < smallestDeltaY) {
-                        smallestDeltaY = deltaY;
-                        replacementIndex = j;
-                    }
-                }
-                distance = abs(x - lastTouch.pointers[replacementIndex].x);
-            } else {
-                // Y looks too close.  Find an older replacement point with a close X.
-                int32_t smallestDeltaX = INT_MAX;
-                for (uint32_t j = 0; j < pointerCount; j++) {
-                    int32_t deltaX = abs(x - lastTouch.pointers[j].x);
-                    if (deltaX < smallestDeltaX) {
-                        smallestDeltaX = deltaX;
-                        replacementIndex = j;
-                    }
-                }
-                distance = abs(y - lastTouch.pointers[replacementIndex].y);
-            }
-
-            // If replacing this pointer would correct a worse error than the previous ones
-            // considered, then use this replacement instead.
-            if (distance > badPointerDistance) {
-                badPointerIndex = i;
-                badPointerReplacementIndex = replacementIndex;
-                badPointerDistance = distance;
-            }
-        }
-
-        // Correct the jumpy pointer if one was found.
-        if (badPointerIndex >= 0) {
-#if DEBUG_HACKS
-            LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)",
-                    badPointerIndex,
-                    lastTouch.pointers[badPointerReplacementIndex].x,
-                    lastTouch.pointers[badPointerReplacementIndex].y);
-#endif
-
-            currentTouch.pointers[badPointerIndex].x =
-                    lastTouch.pointers[badPointerReplacementIndex].x;
-            currentTouch.pointers[badPointerIndex].y =
-                    lastTouch.pointers[badPointerReplacementIndex].y;
-            jumpyTouchFilter.jumpyPointsDropped += 1;
-            return true;
-        }
-    }
-
-    jumpyTouchFilter.jumpyPointsDropped = 0;
-    return false;
-}
-
-/* Special hack for devices that have bad screen data: aggregate and
- * compute averages of the coordinate data, to reduce the amount of
- * jitter seen by applications. */
-void InputDevice::TouchScreenState::applyAveragingTouchFilter() {
-    for (uint32_t currentIndex = 0; currentIndex < currentTouch.pointerCount; currentIndex++) {
-        uint32_t id = currentTouch.pointers[currentIndex].id;
-        int32_t x = currentTouch.pointers[currentIndex].x;
-        int32_t y = currentTouch.pointers[currentIndex].y;
-        int32_t pressure = currentTouch.pointers[currentIndex].pressure;
-
-        if (lastTouch.idBits.hasBit(id)) {
-            // Pointer was down before and is still down now.
-            // Compute average over history trace.
-            uint32_t start = averagingTouchFilter.historyStart[id];
-            uint32_t end = averagingTouchFilter.historyEnd[id];
-
-            int64_t deltaX = x - averagingTouchFilter.historyData[end].pointers[id].x;
-            int64_t deltaY = y - averagingTouchFilter.historyData[end].pointers[id].y;
-            uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
-
-#if DEBUG_HACKS
-            LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld",
-                    id, distance);
-#endif
-
-            if (distance < AVERAGING_DISTANCE_LIMIT) {
-                // Increment end index in preparation for recording new historical data.
-                end += 1;
-                if (end > AVERAGING_HISTORY_SIZE) {
-                    end = 0;
-                }
-
-                // If the end index has looped back to the start index then we have filled
-                // the historical trace up to the desired size so we drop the historical
-                // data at the start of the trace.
-                if (end == start) {
-                    start += 1;
-                    if (start > AVERAGING_HISTORY_SIZE) {
-                        start = 0;
-                    }
-                }
-
-                // Add the raw data to the historical trace.
-                averagingTouchFilter.historyStart[id] = start;
-                averagingTouchFilter.historyEnd[id] = end;
-                averagingTouchFilter.historyData[end].pointers[id].x = x;
-                averagingTouchFilter.historyData[end].pointers[id].y = y;
-                averagingTouchFilter.historyData[end].pointers[id].pressure = pressure;
-
-                // Average over all historical positions in the trace by total pressure.
-                int32_t averagedX = 0;
-                int32_t averagedY = 0;
-                int32_t totalPressure = 0;
-                for (;;) {
-                    int32_t historicalX = averagingTouchFilter.historyData[start].pointers[id].x;
-                    int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].y;
-                    int32_t historicalPressure = averagingTouchFilter.historyData[start]
-                            .pointers[id].pressure;
-
-                    averagedX += historicalX * historicalPressure;
-                    averagedY += historicalY * historicalPressure;
-                    totalPressure += historicalPressure;
-
-                    if (start == end) {
-                        break;
-                    }
-
-                    start += 1;
-                    if (start > AVERAGING_HISTORY_SIZE) {
-                        start = 0;
-                    }
-                }
-
-                averagedX /= totalPressure;
-                averagedY /= totalPressure;
-
-#if DEBUG_HACKS
-                LOGD("AveragingTouchFilter: Pointer id %d - "
-                        "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
-                        averagedX, averagedY);
-#endif
-
-                currentTouch.pointers[currentIndex].x = averagedX;
-                currentTouch.pointers[currentIndex].y = averagedY;
-            } else {
-#if DEBUG_HACKS
-                LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
-#endif
-            }
-        } else {
-#if DEBUG_HACKS
-            LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id);
-#endif
-        }
-
-        // Reset pointer history.
-        averagingTouchFilter.historyStart[id] = 0;
-        averagingTouchFilter.historyEnd[id] = 0;
-        averagingTouchFilter.historyData[0].pointers[id].x = x;
-        averagingTouchFilter.historyData[0].pointers[id].y = y;
-        averagingTouchFilter.historyData[0].pointers[id].pressure = pressure;
-    }
-}
-
-bool InputDevice::TouchScreenState::isPointInsideDisplay(int32_t x, int32_t y) const {
-    if (! parameters.xAxis.valid || ! parameters.yAxis.valid) {
-        // Assume all points on a touch screen without valid axis parameters are
-        // inside the display.
-        return true;
-    }
-
-    return x >= parameters.xAxis.minValue
-        && x <= parameters.xAxis.maxValue
-        && y >= parameters.yAxis.minValue
-        && y <= parameters.yAxis.maxValue;
-}
-
-const InputDevice::VirtualKey* InputDevice::TouchScreenState::findVirtualKeyHit() const {
-    int32_t x = currentTouch.pointers[0].x;
-    int32_t y = currentTouch.pointers[0].y;
-    for (size_t i = 0; i < virtualKeys.size(); i++) {
-        const InputDevice::VirtualKey& virtualKey = virtualKeys[i];
-
-#if DEBUG_VIRTUAL_KEYS
-        LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
-                "left=%d, top=%d, right=%d, bottom=%d",
-                x, y,
-                virtualKey.keyCode, virtualKey.scanCode,
-                virtualKey.hitLeft, virtualKey.hitTop,
-                virtualKey.hitRight, virtualKey.hitBottom);
-#endif
-
-        if (virtualKey.isHit(x, y)) {
-            return & virtualKey;
-        }
-    }
-
-    return NULL;
-}
-
-
-// --- InputDevice::SingleTouchScreenState ---
-
-void InputDevice::SingleTouchScreenState::reset() {
-    accumulator.clear();
-    current.down = false;
-    current.x = 0;
-    current.y = 0;
-    current.pressure = 0;
-    current.size = 0;
-}
-
-
-// --- InputDevice::MultiTouchScreenState ---
-
-void InputDevice::MultiTouchScreenState::reset() {
-    accumulator.clear();
-}
-
-} // namespace android
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
index e1d15a4..bf23479 100644
--- a/libs/ui/InputManager.cpp
+++ b/libs/ui/InputManager.cpp
@@ -89,26 +89,35 @@
     mDispatcher->preemptInputDispatch();
 }
 
-void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) const {
-    mReader->getCurrentInputConfiguration(outConfiguration);
+void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) {
+    mReader->getInputConfiguration(outConfiguration);
 }
 
-int32_t InputManager::getScanCodeState(int32_t deviceId, int32_t deviceClasses,
-        int32_t scanCode) const {
-    return mReader->getCurrentScanCodeState(deviceId, deviceClasses, scanCode);
+status_t InputManager::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+    return mReader->getInputDeviceInfo(deviceId, outDeviceInfo);
 }
 
-int32_t InputManager::getKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-        int32_t keyCode) const {
-    return mReader->getCurrentKeyCodeState(deviceId, deviceClasses, keyCode);
+void InputManager::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
+    mReader->getInputDeviceIds(outDeviceIds);
 }
 
-int32_t InputManager::getSwitchState(int32_t deviceId, int32_t deviceClasses, int32_t sw) const {
-    return mReader->getCurrentSwitchState(deviceId, deviceClasses, sw);
+int32_t InputManager::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+        int32_t scanCode) {
+    return mReader->getScanCodeState(deviceId, sourceMask, scanCode);
 }
 
-bool InputManager::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const {
-    return mReader->hasKeys(numCodes, keyCodes, outFlags);
+int32_t InputManager::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+        int32_t keyCode) {
+    return mReader->getKeyCodeState(deviceId, sourceMask, keyCode);
+}
+
+int32_t InputManager::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) {
+    return mReader->getSwitchState(deviceId, sourceMask, sw);
+}
+
+bool InputManager::hasKeys(int32_t deviceId, uint32_t sourceMask,
+        size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+    return mReader->hasKeys(deviceId, sourceMask, numCodes, keyCodes, outFlags);
 }
 
 } // namespace android
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 30e391f..c5183e4 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -31,10 +31,6 @@
 #include <limits.h>
 #include <math.h>
 
-/** Amount that trackball needs to move in order to generate a key event. */
-#define TRACKBALL_MOVEMENT_THRESHOLD 6
-
-
 namespace android {
 
 // --- Static Functions ---
@@ -115,17 +111,21 @@
     return keyCode;
 }
 
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+    return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
 
 // --- InputReader ---
 
 InputReader::InputReader(const sp<EventHubInterface>& eventHub,
         const sp<InputReaderPolicyInterface>& policy,
         const sp<InputDispatcherInterface>& dispatcher) :
-        mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) {
+        mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher),
+        mGlobalMetaState(0) {
     configureExcludedDevices();
-    resetGlobalMetaState();
-    resetDisplayProperties();
-    updateExportedVirtualKeyState();
+    updateGlobalMetaState();
+    updateInputConfiguration();
 }
 
 InputReader::~InputReader() {
@@ -136,12 +136,7 @@
 
 void InputReader::loopOnce() {
     RawEvent rawEvent;
-    mEventHub->getEvent(& rawEvent.deviceId, & rawEvent.type, & rawEvent.scanCode,
-            & rawEvent.keyCode, & rawEvent.flags, & rawEvent.value, & rawEvent.when);
-
-    // Replace the event timestamp so it is in same timebase as java.lang.System.nanoTime()
-    // and android.os.SystemClock.uptimeMillis() as expected by the rest of the system.
-    rawEvent.when = systemTime(SYSTEM_TIME_MONOTONIC);
+    mEventHub->getEvent(& rawEvent);
 
 #if DEBUG_RAW_EVENTS
     LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",
@@ -155,621 +150,1371 @@
 void InputReader::process(const RawEvent* rawEvent) {
     switch (rawEvent->type) {
     case EventHubInterface::DEVICE_ADDED:
-        handleDeviceAdded(rawEvent);
+        addDevice(rawEvent->when, rawEvent->deviceId);
         break;
 
     case EventHubInterface::DEVICE_REMOVED:
-        handleDeviceRemoved(rawEvent);
+        removeDevice(rawEvent->when, rawEvent->deviceId);
         break;
 
-    case EV_SYN:
-        handleSync(rawEvent);
-        break;
-
-    case EV_KEY:
-        handleKey(rawEvent);
-        break;
-
-    case EV_REL:
-        handleRelativeMotion(rawEvent);
-        break;
-
-    case EV_ABS:
-        handleAbsoluteMotion(rawEvent);
-        break;
-
-    case EV_SW:
-        handleSwitch(rawEvent);
+    default:
+        consumeEvent(rawEvent);
         break;
     }
 }
 
-void InputReader::handleDeviceAdded(const RawEvent* rawEvent) {
-    InputDevice* device = getDevice(rawEvent->deviceId);
-    if (device) {
-        LOGW("Ignoring spurious device added event for deviceId %d.", rawEvent->deviceId);
+void InputReader::addDevice(nsecs_t when, int32_t deviceId) {
+    String8 name = mEventHub->getDeviceName(deviceId);
+    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+
+    InputDevice* device = createDevice(deviceId, name, classes);
+    device->configure();
+
+    bool added = false;
+    { // acquire device registry writer lock
+        RWLock::AutoWLock _wl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex < 0) {
+            mDevices.add(deviceId, device);
+            added = true;
+        }
+    } // release device registry writer lock
+
+    if (! added) {
+        LOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+        delete device;
         return;
     }
 
-    addDevice(rawEvent->when, rawEvent->deviceId);
+    if (device->isIgnored()) {
+        LOGI("Device added: id=0x%x, name=%s (ignored non-input device)",
+                deviceId, name.string());
+    } else {
+        LOGI("Device added: id=0x%x, name=%s, sources=%08x",
+                deviceId, name.string(), device->getSources());
+    }
+
+    handleConfigurationChanged(when);
 }
 
-void InputReader::handleDeviceRemoved(const RawEvent* rawEvent) {
-    InputDevice* device = getDevice(rawEvent->deviceId);
-    if (! device) {
-        LOGW("Ignoring spurious device removed event for deviceId %d.", rawEvent->deviceId);
+void InputReader::removeDevice(nsecs_t when, int32_t deviceId) {
+    bool removed = false;
+    InputDevice* device = NULL;
+    { // acquire device registry writer lock
+        RWLock::AutoWLock _wl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex >= 0) {
+            device = mDevices.valueAt(deviceIndex);
+            mDevices.removeItemsAt(deviceIndex, 1);
+            removed = true;
+        }
+    } // release device registry writer lock
+
+    if (! removed) {
+        LOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
         return;
     }
 
-    removeDevice(rawEvent->when, device);
+    device->reset();
+
+    if (device->isIgnored()) {
+        LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)",
+                device->getId(), device->getName().string());
+    } else {
+        LOGI("Device removed: id=0x%x, name=%s, sources=%08x",
+                device->getId(), device->getName().string(), device->getSources());
+    }
+
+    delete device;
+
+    handleConfigurationChanged(when);
 }
 
-void InputReader::handleSync(const RawEvent* rawEvent) {
-    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
-    if (! device) return;
+InputDevice* InputReader::createDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+    InputDevice* device = new InputDevice(this, deviceId, name);
 
-    if (rawEvent->scanCode == SYN_MT_REPORT) {
-        // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
-        // We drop pointers with pressure <= 0 since that indicates they are not down.
-        if (device->isMultiTouchScreen()) {
-            uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount;
+    const int32_t associatedDisplayId = 0; // FIXME: hardcoded for current single-display devices
 
-            if (device->multiTouchScreen.accumulator.pointers[pointerIndex].fields) {
-                if (pointerIndex == MAX_POINTERS) {
-                    LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
-                            MAX_POINTERS);
-                } else {
-                    pointerIndex += 1;
-                    device->multiTouchScreen.accumulator.pointerCount = pointerIndex;
+    // Switch-like devices.
+    if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+        device->addMapper(new SwitchInputMapper(device));
+    }
+
+    // Keyboard-like devices.
+    uint32_t keyboardSources = 0;
+    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+        keyboardSources |= AINPUT_SOURCE_KEYBOARD;
+    }
+    if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+    }
+    if (classes & INPUT_DEVICE_CLASS_DPAD) {
+        keyboardSources |= AINPUT_SOURCE_DPAD;
+    }
+    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+        keyboardSources |= AINPUT_SOURCE_GAMEPAD;
+    }
+
+    if (keyboardSources != 0) {
+        device->addMapper(new KeyboardInputMapper(device,
+                associatedDisplayId, keyboardSources, keyboardType));
+    }
+
+    // Trackball-like devices.
+    if (classes & INPUT_DEVICE_CLASS_TRACKBALL) {
+        device->addMapper(new TrackballInputMapper(device, associatedDisplayId));
+    }
+
+    // Touchscreen-like devices.
+    if (classes & INPUT_DEVICE_CLASS_TOUCHSCREEN_MT) {
+        device->addMapper(new MultiTouchInputMapper(device, associatedDisplayId));
+    } else if (classes & INPUT_DEVICE_CLASS_TOUCHSCREEN) {
+        device->addMapper(new SingleTouchInputMapper(device, associatedDisplayId));
+    }
+
+    return device;
+}
+
+void InputReader::consumeEvent(const RawEvent* rawEvent) {
+    int32_t deviceId = rawEvent->deviceId;
+
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex < 0) {
+            LOGW("Discarding event for unknown deviceId %d.", deviceId);
+            return;
+        }
+
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        if (device->isIgnored()) {
+            //LOGD("Discarding event for ignored deviceId %d.", deviceId);
+            return;
+        }
+
+        device->process(rawEvent);
+    } // release device registry reader lock
+}
+
+void InputReader::handleConfigurationChanged(nsecs_t when) {
+    // Reset global meta state because it depends on the list of all configured devices.
+    updateGlobalMetaState();
+
+    // Update input configuration.
+    updateInputConfiguration();
+
+    // Enqueue configuration changed.
+    mDispatcher->notifyConfigurationChanged(when);
+}
+
+void InputReader::configureExcludedDevices() {
+    Vector<String8> excludedDeviceNames;
+    mPolicy->getExcludedDeviceNames(excludedDeviceNames);
+
+    for (size_t i = 0; i < excludedDeviceNames.size(); i++) {
+        mEventHub->addExcludedDevice(excludedDeviceNames[i]);
+    }
+}
+
+void InputReader::updateGlobalMetaState() {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        mGlobalMetaState = 0;
+
+        { // acquire device registry reader lock
+            RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+            for (size_t i = 0; i < mDevices.size(); i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                mGlobalMetaState |= device->getMetaState();
+            }
+        } // release device registry reader lock
+    } // release state lock
+}
+
+int32_t InputReader::getGlobalMetaState() {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        return mGlobalMetaState;
+    } // release state lock
+}
+
+void InputReader::updateInputConfiguration() {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH;
+        int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS;
+        int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV;
+        { // acquire device registry reader lock
+            RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+            InputDeviceInfo deviceInfo;
+            for (size_t i = 0; i < mDevices.size(); i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                device->getDeviceInfo(& deviceInfo);
+                uint32_t sources = deviceInfo.getSources();
+
+                if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) {
+                    touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER;
+                }
+                if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) {
+                    navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL;
+                } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) {
+                    navigationConfig = InputConfiguration::NAVIGATION_DPAD;
+                }
+                if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+                    keyboardConfig = InputConfiguration::KEYBOARD_QWERTY;
                 }
             }
+        } // release device registry reader lock
 
-            device->multiTouchScreen.accumulator.pointers[pointerIndex].clear();
-        }
-    } else if (rawEvent->scanCode == SYN_REPORT) {
-        // General Sync: The driver has returned all data for the current event update.
-        if (device->isMultiTouchScreen()) {
-            if (device->multiTouchScreen.accumulator.isDirty()) {
-                onMultiTouchScreenStateChanged(rawEvent->when, device);
-                device->multiTouchScreen.accumulator.clear();
-            }
-        } else if (device->isSingleTouchScreen()) {
-            if (device->singleTouchScreen.accumulator.isDirty()) {
-                onSingleTouchScreenStateChanged(rawEvent->when, device);
-                device->singleTouchScreen.accumulator.clear();
-            }
+        mInputConfiguration.touchScreen = touchScreenConfig;
+        mInputConfiguration.keyboard = keyboardConfig;
+        mInputConfiguration.navigation = navigationConfig;
+    } // release state lock
+}
+
+void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
+    { // acquire state lock
+        AutoMutex _l(mStateLock);
+
+        *outConfiguration = mInputConfiguration;
+    } // release state lock
+}
+
+status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+        if (deviceIndex < 0) {
+            return NAME_NOT_FOUND;
         }
 
-        if (device->trackball.accumulator.isDirty()) {
-            onTrackballStateChanged(rawEvent->when, device);
-            device->trackball.accumulator.clear();
+        InputDevice* device = mDevices.valueAt(deviceIndex);
+        if (device->isIgnored()) {
+            return NAME_NOT_FOUND;
         }
+
+        device->getDeviceInfo(outDeviceInfo);
+        return OK;
+    } // release device registy reader lock
+}
+
+void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
+    outDeviceIds.clear();
+
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        size_t numDevices = mDevices.size();
+        for (size_t i = 0; i < numDevices; i++) {
+            InputDevice* device = mDevices.valueAt(i);
+            if (! device->isIgnored()) {
+                outDeviceIds.add(device->getId());
+            }
+        }
+    } // release device registy reader lock
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+        int32_t keyCode) {
+    return getState(deviceId, sourceMask, keyCode, & InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+        int32_t scanCode) {
+    return getState(deviceId, sourceMask, scanCode, & InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+    return getState(deviceId, sourceMask, switchCode, & InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getState(int32_t deviceId, uint32_t sourceMask, int32_t code,
+        GetStateFunc getStateFunc) {
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+
+        int32_t result = AKEY_STATE_UNKNOWN;
+        if (deviceId >= 0) {
+            ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+            if (deviceIndex >= 0) {
+                InputDevice* device = mDevices.valueAt(deviceIndex);
+                if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                    result = (device->*getStateFunc)(sourceMask, code);
+                }
+            }
+        } else {
+            size_t numDevices = mDevices.size();
+            for (size_t i = 0; i < numDevices; i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                    result = (device->*getStateFunc)(sourceMask, code);
+                    if (result >= AKEY_STATE_DOWN) {
+                        return result;
+                    }
+                }
+            }
+        }
+        return result;
+    } // release device registy reader lock
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
+        size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+    memset(outFlags, 0, numCodes);
+    return markSupportedKeyCodes(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    { // acquire device registry reader lock
+        RWLock::AutoRLock _rl(mDeviceRegistryLock);
+        bool result = false;
+        if (deviceId >= 0) {
+            ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+            if (deviceIndex >= 0) {
+                InputDevice* device = mDevices.valueAt(deviceIndex);
+                if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                    result = device->markSupportedKeyCodes(sourceMask,
+                            numCodes, keyCodes, outFlags);
+                }
+            }
+        } else {
+            size_t numDevices = mDevices.size();
+            for (size_t i = 0; i < numDevices; i++) {
+                InputDevice* device = mDevices.valueAt(i);
+                if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+                    result |= device->markSupportedKeyCodes(sourceMask,
+                            numCodes, keyCodes, outFlags);
+                }
+            }
+        }
+        return result;
+    } // release device registy reader lock
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+        Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+    mReader->loopOnce();
+    return true;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name) :
+        mContext(context), mId(id), mName(name), mSources(0) {
+}
+
+InputDevice::~InputDevice() {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        delete mMappers[i];
+    }
+    mMappers.clear();
+}
+
+void InputDevice::addMapper(InputMapper* mapper) {
+    mMappers.add(mapper);
+}
+
+void InputDevice::configure() {
+    mSources = 0;
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->configure();
+        mSources |= mapper->getSources();
     }
 }
 
-void InputReader::handleKey(const RawEvent* rawEvent) {
-    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
-    if (! device) return;
+void InputDevice::reset() {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->reset();
+    }
+}
 
-    bool down = rawEvent->value != 0;
-    int32_t scanCode = rawEvent->scanCode;
+void InputDevice::process(const RawEvent* rawEvent) {
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->process(rawEvent);
+    }
+}
 
-    if (device->isSingleTouchScreen()) {
-        switch (rawEvent->scanCode) {
-        case BTN_TOUCH:
-            device->singleTouchScreen.accumulator.fields |=
-                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH;
-            device->singleTouchScreen.accumulator.btnTouch = down;
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+    outDeviceInfo->initialize(mId, mName);
+
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        mapper->populateDeviceInfo(outDeviceInfo);
+    }
+}
+
+int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState);
+}
+
+int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    return getState(sourceMask, scanCode, & InputMapper::getScanCodeState);
+}
+
+int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return getState(sourceMask, switchCode, & InputMapper::getSwitchState);
+}
+
+int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) {
+    int32_t result = AKEY_STATE_UNKNOWN;
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+            result = (mapper->*getStateFunc)(sourceMask, code);
+            if (result >= AKEY_STATE_DOWN) {
+                return result;
+            }
+        }
+    }
+    return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    bool result = false;
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+            result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+        }
+    }
+    return result;
+}
+
+int32_t InputDevice::getMetaState() {
+    int32_t result = 0;
+    size_t numMappers = mMappers.size();
+    for (size_t i = 0; i < numMappers; i++) {
+        InputMapper* mapper = mMappers[i];
+        result |= mapper->getMetaState();
+    }
+    return result;
+}
+
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) :
+        mDevice(device), mContext(device->getContext()) {
+}
+
+InputMapper::~InputMapper() {
+}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    info->addSource(getSources());
+}
+
+void InputMapper::configure() {
+}
+
+void InputMapper::reset() {
+}
+
+int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return AKEY_STATE_UNKNOWN;
+}
+
+bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    return false;
+}
+
+int32_t InputMapper::getMetaState() {
+    return 0;
+}
+
+bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) {
+    if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) {
+        getDispatcher()->notifyAppSwitchComing(when);
+    }
+
+    return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH;
+}
+
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device) :
+        InputMapper(device) {
+}
+
+SwitchInputMapper::~SwitchInputMapper() {
+}
+
+uint32_t SwitchInputMapper::getSources() {
+    return 0;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_SW:
+        processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value);
+        break;
+    }
+}
+
+void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) {
+    uint32_t policyFlags = 0;
+    int32_t policyActions = getPolicy()->interceptSwitch(
+            when, switchCode, switchValue, policyFlags);
+
+    applyStandardPolicyActions(when, policyActions);
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+    return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device, int32_t associatedDisplayId,
+        uint32_t sources, int32_t keyboardType) :
+        InputMapper(device), mAssociatedDisplayId(associatedDisplayId), mSources(sources),
+        mKeyboardType(keyboardType) {
+    initialize();
+}
+
+KeyboardInputMapper::~KeyboardInputMapper() {
+}
+
+void KeyboardInputMapper::initialize() {
+    mMetaState = AMETA_NONE;
+    mDownTime = 0;
+}
+
+uint32_t KeyboardInputMapper::getSources() {
+    return mSources;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    info->setKeyboardType(mKeyboardType);
+}
+
+void KeyboardInputMapper::reset() {
+    // Synthesize key up event on reset if keys are currently down.
+    while (! mKeyDowns.isEmpty()) {
+        const KeyDown& keyDown = mKeyDowns.top();
+        nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+        processKey(when, false, keyDown.keyCode, keyDown.scanCode, 0);
+    }
+
+    InputMapper::reset();
+
+    // Reinitialize.
+    initialize();
+    getContext()->updateGlobalMetaState();
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_KEY: {
+        int32_t scanCode = rawEvent->scanCode;
+        if (isKeyboardOrGamepadKey(scanCode)) {
+            processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode,
+                    rawEvent->flags);
+        }
+        break;
+    }
+    }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+    return scanCode < BTN_MOUSE
+        || scanCode >= KEY_OK
+        || (scanCode >= BTN_GAMEPAD && scanCode < BTN_DIGI);
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+        uint32_t policyFlags) {
+    if (down) {
+        // Rotate key codes according to orientation.
+        if (mAssociatedDisplayId >= 0) {
+            int32_t orientation;
+            if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) {
+                return;
+            }
+
+            keyCode = rotateKeyCode(keyCode, orientation);
+        }
+
+        // Add key down.
+        ssize_t keyDownIndex = findKeyDown(scanCode);
+        if (keyDownIndex >= 0) {
+            // key repeat, be sure to use same keycode as before in case of rotation
+            keyCode = mKeyDowns.top().keyCode;
+        } else {
+            // key down
+            mKeyDowns.push();
+            KeyDown& keyDown = mKeyDowns.editTop();
+            keyDown.keyCode = keyCode;
+            keyDown.scanCode = scanCode;
+        }
+    } else {
+        // Remove key down.
+        ssize_t keyDownIndex = findKeyDown(scanCode);
+        if (keyDownIndex >= 0) {
+            // key up, be sure to use same keycode as before in case of rotation
+            keyCode = mKeyDowns.top().keyCode;
+            mKeyDowns.removeAt(size_t(keyDownIndex));
+        } else {
+            // key was not actually down
+            LOGI("Dropping key up from device %s because the key was not down.  "
+                    "keyCode=%d, scanCode=%d",
+                    getDeviceName().string(), keyCode, scanCode);
             return;
         }
     }
 
-    if (device->isTrackball()) {
-        switch (rawEvent->scanCode) {
-        case BTN_MOUSE:
-            device->trackball.accumulator.fields |=
-                    InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
-            device->trackball.accumulator.btnMouse = down;
-
-            // Process the trackball change now since we may not receive a sync immediately.
-            onTrackballStateChanged(rawEvent->when, device);
-            device->trackball.accumulator.clear();
-            return;
-        }
-    }
-
-    if (device->isKeyboard()) {
-        int32_t keyCode = rawEvent->keyCode;
-        onKey(rawEvent->when, device, down, keyCode, scanCode, rawEvent->flags);
-    }
-}
-
-void InputReader::handleRelativeMotion(const RawEvent* rawEvent) {
-    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
-    if (! device) return;
-
-    if (device->isTrackball()) {
-        switch (rawEvent->scanCode) {
-        case REL_X:
-            device->trackball.accumulator.fields |=
-                    InputDevice::TrackballState::Accumulator::FIELD_REL_X;
-            device->trackball.accumulator.relX = rawEvent->value;
-            break;
-        case REL_Y:
-            device->trackball.accumulator.fields |=
-                    InputDevice::TrackballState::Accumulator::FIELD_REL_Y;
-            device->trackball.accumulator.relY = rawEvent->value;
-            break;
-        }
-    }
-}
-
-void InputReader::handleAbsoluteMotion(const RawEvent* rawEvent) {
-    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
-    if (! device) return;
-
-    if (device->isMultiTouchScreen()) {
-        uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount;
-        InputDevice::MultiTouchScreenState::Accumulator::Pointer* pointer =
-                & device->multiTouchScreen.accumulator.pointers[pointerIndex];
-
-        switch (rawEvent->scanCode) {
-        case ABS_MT_POSITION_X:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X;
-            pointer->absMTPositionX = rawEvent->value;
-            break;
-        case ABS_MT_POSITION_Y:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y;
-            pointer->absMTPositionY = rawEvent->value;
-            break;
-        case ABS_MT_TOUCH_MAJOR:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
-            pointer->absMTTouchMajor = rawEvent->value;
-            break;
-        case ABS_MT_TOUCH_MINOR:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
-            pointer->absMTTouchMinor = rawEvent->value;
-            break;
-        case ABS_MT_WIDTH_MAJOR:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
-            pointer->absMTWidthMajor = rawEvent->value;
-            break;
-        case ABS_MT_WIDTH_MINOR:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
-            pointer->absMTWidthMinor = rawEvent->value;
-            break;
-        case ABS_MT_ORIENTATION:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_ORIENTATION;
-            pointer->absMTOrientation = rawEvent->value;
-            break;
-        case ABS_MT_TRACKING_ID:
-            pointer->fields |=
-                    InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TRACKING_ID;
-            pointer->absMTTrackingId = rawEvent->value;
-            break;
-        }
-    } else if (device->isSingleTouchScreen()) {
-        switch (rawEvent->scanCode) {
-        case ABS_X:
-            device->singleTouchScreen.accumulator.fields |=
-                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X;
-            device->singleTouchScreen.accumulator.absX = rawEvent->value;
-            break;
-        case ABS_Y:
-            device->singleTouchScreen.accumulator.fields |=
-                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y;
-            device->singleTouchScreen.accumulator.absY = rawEvent->value;
-            break;
-        case ABS_PRESSURE:
-            device->singleTouchScreen.accumulator.fields |=
-                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE;
-            device->singleTouchScreen.accumulator.absPressure = rawEvent->value;
-            break;
-        case ABS_TOOL_WIDTH:
-            device->singleTouchScreen.accumulator.fields |=
-                    InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH;
-            device->singleTouchScreen.accumulator.absToolWidth = rawEvent->value;
-            break;
-        }
-    }
-}
-
-void InputReader::handleSwitch(const RawEvent* rawEvent) {
-    InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
-    if (! device) return;
-
-    onSwitch(rawEvent->when, device, rawEvent->scanCode, rawEvent->value);
-}
-
-void InputReader::onKey(nsecs_t when, InputDevice* device,
-        bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
-    /* Refresh display properties so we can rotate key codes according to display orientation */
-
-    if (! refreshDisplayProperties()) {
-        return;
-    }
-
-    /* Update device state */
-
-    int32_t oldMetaState = device->keyboard.current.metaState;
+    int32_t oldMetaState = mMetaState;
     int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
     if (oldMetaState != newMetaState) {
-        device->keyboard.current.metaState = newMetaState;
-        resetGlobalMetaState();
+        mMetaState = newMetaState;
+        getContext()->updateGlobalMetaState();
     }
 
-    // FIXME if we send a down event about a rotated key press we should ensure that we send
-    //       a corresponding up event about the rotated key press even if the orientation
-    //       has changed in the meantime
-    keyCode = rotateKeyCode(keyCode, mDisplayOrientation);
+    /* Apply policy. */
 
-    if (down) {
-        device->keyboard.current.downTime = when;
-    }
+    int32_t policyActions = getPolicy()->interceptKey(when,
+            getDeviceId(), down, keyCode, scanCode, policyFlags);
 
-    /* Apply policy */
-
-    int32_t policyActions = mPolicy->interceptKey(when, device->id,
-            down, keyCode, scanCode, policyFlags);
-
-    if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
+    if (! applyStandardPolicyActions(when, policyActions)) {
         return; // event dropped
     }
 
-    /* Enqueue key event for dispatch */
+    /* Enqueue key event for dispatch. */
 
     int32_t keyEventAction;
     if (down) {
-        device->keyboard.current.downTime = when;
+        mDownTime = when;
         keyEventAction = AKEY_EVENT_ACTION_DOWN;
     } else {
         keyEventAction = AKEY_EVENT_ACTION_UP;
     }
 
     int32_t keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM;
-    if (policyActions & InputReaderPolicyInterface::ACTION_WOKE_HERE) {
+    if (policyFlags & POLICY_FLAG_WOKE_HERE) {
         keyEventFlags = keyEventFlags | AKEY_EVENT_FLAG_WOKE_HERE;
     }
 
-    mDispatcher->notifyKey(when, device->id, AINPUT_SOURCE_KEYBOARD, policyFlags,
+    getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
             keyEventAction, keyEventFlags, keyCode, scanCode,
-            device->keyboard.current.metaState,
-            device->keyboard.current.downTime);
+            mMetaState, mDownTime);
 }
 
-void InputReader::onSwitch(nsecs_t when, InputDevice* device, int32_t switchCode,
-        int32_t switchValue) {
-    int32_t policyActions = mPolicy->interceptSwitch(when, switchCode, switchValue);
-
-    uint32_t policyFlags = 0;
-    applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags);
+ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
+    size_t n = mKeyDowns.size();
+    for (size_t i = 0; i < n; i++) {
+        if (mKeyDowns[i].scanCode == scanCode) {
+            return i;
+        }
+    }
+    return -1;
 }
 
-void InputReader::onMultiTouchScreenStateChanged(nsecs_t when,
-        InputDevice* device) {
-    static const uint32_t REQUIRED_FIELDS =
-            InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X
-            | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y
-            | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR
-            | InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    return getEventHub()->getKeyCodeState(getDeviceId(), keyCode);
+}
 
-    /* Refresh display properties so we can map touch screen coords into display coords */
+int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+}
 
-    if (! refreshDisplayProperties()) {
-        return;
+bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags);
+}
+
+int32_t KeyboardInputMapper::getMetaState() {
+    return mMetaState;
+}
+
+
+// --- TrackballInputMapper ---
+
+TrackballInputMapper::TrackballInputMapper(InputDevice* device, int32_t associatedDisplayId) :
+        InputMapper(device), mAssociatedDisplayId(associatedDisplayId) {
+    mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+    mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+    mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+    mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+
+    initialize();
+}
+
+TrackballInputMapper::~TrackballInputMapper() {
+}
+
+uint32_t TrackballInputMapper::getSources() {
+    return AINPUT_SOURCE_TRACKBALL;
+}
+
+void TrackballInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    info->addMotionRange(AINPUT_MOTION_RANGE_X, -1.0f, 1.0f, 0.0f, mXScale);
+    info->addMotionRange(AINPUT_MOTION_RANGE_Y, -1.0f, 1.0f, 0.0f, mYScale);
+}
+
+void TrackballInputMapper::initialize() {
+    mAccumulator.clear();
+
+    mDown = false;
+    mDownTime = 0;
+}
+
+void TrackballInputMapper::reset() {
+    // Synthesize trackball button up event on reset if trackball button is currently down.
+    if (mDown) {
+        nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+        mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE;
+        mAccumulator.btnMouse = false;
+        sync(when);
     }
 
-    /* Update device state */
+    InputMapper::reset();
 
-    InputDevice::MultiTouchScreenState* in = & device->multiTouchScreen;
-    InputDevice::TouchData* out = & device->touchScreen.currentTouch;
+    // Reinitialize.
+    initialize();
+}
 
-    uint32_t inCount = in->accumulator.pointerCount;
-    uint32_t outCount = 0;
-    bool havePointerIds = true;
+void TrackballInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_KEY:
+        switch (rawEvent->scanCode) {
+        case BTN_MOUSE:
+            mAccumulator.fields |= Accumulator::FIELD_BTN_MOUSE;
+            mAccumulator.btnMouse = rawEvent->value != 0;
 
-    out->clear();
-
-    for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
-        uint32_t fields = in->accumulator.pointers[inIndex].fields;
-
-        if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
-#if DEBUG_POINTERS
-            LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d",
-                    inIndex, fields);
-            continue;
-#endif
+            sync(rawEvent->when);
+            mAccumulator.clear();
+            break;
         }
+        break;
 
-        if (in->accumulator.pointers[inIndex].absMTTouchMajor <= 0) {
-            // Pointer is not down.  Drop it.
-            continue;
+    case EV_REL:
+        switch (rawEvent->scanCode) {
+        case REL_X:
+            mAccumulator.fields |= Accumulator::FIELD_REL_X;
+            mAccumulator.relX = rawEvent->value;
+            break;
+        case REL_Y:
+            mAccumulator.fields |= Accumulator::FIELD_REL_Y;
+            mAccumulator.relY = rawEvent->value;
+            break;
         }
+        break;
 
-        out->pointers[outCount].x = in->accumulator.pointers[inIndex].absMTPositionX;
-        out->pointers[outCount].y = in->accumulator.pointers[inIndex].absMTPositionY;
-
-        out->pointers[outCount].touchMajor = in->accumulator.pointers[inIndex].absMTTouchMajor;
-        out->pointers[outCount].touchMinor = (fields
-                & InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MINOR) != 0
-                ? in->accumulator.pointers[inIndex].absMTTouchMinor
-                        : in->accumulator.pointers[inIndex].absMTTouchMajor;
-
-        out->pointers[outCount].toolMajor = in->accumulator.pointers[inIndex].absMTWidthMajor;
-        out->pointers[outCount].toolMinor = (fields
-                & InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MINOR) != 0
-                ? in->accumulator.pointers[inIndex].absMTWidthMinor
-                        : in->accumulator.pointers[inIndex].absMTWidthMajor;
-
-        out->pointers[outCount].orientation = (fields
-                & InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_ORIENTATION) != 0
-                ? in->accumulator.pointers[inIndex].absMTOrientation : 0;
-
-        // Derive an approximation of pressure and size.
-        // FIXME assignment of pressure may be incorrect, probably better to let
-        // pressure = touch / width.  Later on we pass width to MotionEvent as a size, which
-        // isn't quite right either.  Should be using touch for that.
-        out->pointers[outCount].pressure = in->accumulator.pointers[inIndex].absMTTouchMajor;
-        out->pointers[outCount].size = in->accumulator.pointers[inIndex].absMTWidthMajor;
-
-        if (havePointerIds) {
-            if (fields & InputDevice::MultiTouchScreenState::Accumulator::
-                    FIELD_ABS_MT_TRACKING_ID) {
-                uint32_t id = uint32_t(in->accumulator.pointers[inIndex].absMTTrackingId);
-
-                if (id > MAX_POINTER_ID) {
-#if DEBUG_POINTERS
-                    LOGD("Pointers: Ignoring driver provided pointer id %d because "
-                            "it is larger than max supported id %d for optimizations",
-                            id, MAX_POINTER_ID);
-#endif
-                    havePointerIds = false;
-                }
-                else {
-                    out->pointers[outCount].id = id;
-                    out->idToIndex[id] = outCount;
-                    out->idBits.markBit(id);
-                }
-            } else {
-                havePointerIds = false;
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_REPORT:
+            if (mAccumulator.isDirty()) {
+                sync(rawEvent->when);
+                mAccumulator.clear();
             }
+            break;
         }
-
-        outCount += 1;
+        break;
     }
-
-    out->pointerCount = outCount;
-
-    onTouchScreenChanged(when, device, havePointerIds);
 }
 
-void InputReader::onSingleTouchScreenStateChanged(nsecs_t when,
-        InputDevice* device) {
-    /* Refresh display properties so we can map touch screen coords into display coords */
+void TrackballInputMapper::sync(nsecs_t when) {
+    /* Get display properties so for rotation based on display orientation. */
 
-    if (! refreshDisplayProperties()) {
-        return;
+    int32_t orientation;
+    if (mAssociatedDisplayId >= 0) {
+        if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, NULL, NULL, & orientation)) {
+            return;
+        }
+    } else {
+        orientation = InputReaderPolicyInterface::ROTATION_0;
     }
 
-    /* Update device state */
+    /* Update saved trackball state */
 
-    InputDevice::SingleTouchScreenState* in = & device->singleTouchScreen;
-    InputDevice::TouchData* out = & device->touchScreen.currentTouch;
+    uint32_t fields = mAccumulator.fields;
+    bool downChanged = fields & Accumulator::FIELD_BTN_MOUSE;
 
-    uint32_t fields = in->accumulator.fields;
-
-    if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH) {
-        in->current.down = in->accumulator.btnTouch;
+    if (downChanged) {
+        if (mAccumulator.btnMouse) {
+            mDown = true;
+            mDownTime = when;
+        } else {
+            mDown = false;
+        }
     }
 
-    if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X) {
-        in->current.x = in->accumulator.absX;
-    }
-
-    if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y) {
-        in->current.y = in->accumulator.absY;
-    }
-
-    if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE) {
-        in->current.pressure = in->accumulator.absPressure;
-    }
-
-    if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH) {
-        in->current.size = in->accumulator.absToolWidth;
-    }
-
-    out->clear();
-
-    if (in->current.down) {
-        out->pointerCount = 1;
-        out->pointers[0].id = 0;
-        out->pointers[0].x = in->current.x;
-        out->pointers[0].y = in->current.y;
-        out->pointers[0].pressure = in->current.pressure;
-        out->pointers[0].size = in->current.size;
-        out->pointers[0].touchMajor = in->current.pressure;
-        out->pointers[0].touchMinor = in->current.pressure;
-        out->pointers[0].toolMajor = in->current.size;
-        out->pointers[0].toolMinor = in->current.size;
-        out->pointers[0].orientation = 0;
-        out->idToIndex[0] = 0;
-        out->idBits.markBit(0);
-    }
-
-    onTouchScreenChanged(when, device, true);
-}
-
-void InputReader::onTouchScreenChanged(nsecs_t when,
-        InputDevice* device, bool havePointerIds) {
     /* Apply policy */
 
-    int32_t policyActions = mPolicy->interceptTouch(when);
+    uint32_t policyFlags = 0;
+    int32_t policyActions = getPolicy()->interceptGeneric(when, policyFlags);
+
+    if (! applyStandardPolicyActions(when, policyActions)) {
+        return; // event dropped
+    }
+
+    /* Enqueue motion event for dispatch. */
+
+    int32_t motionEventAction;
+    if (downChanged) {
+        motionEventAction = mDown ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+    } else {
+        motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+    }
+
+    int32_t pointerId = 0;
+    PointerCoords pointerCoords;
+    pointerCoords.x = fields & Accumulator::FIELD_REL_X
+            ? mAccumulator.relX * mXScale : 0;
+    pointerCoords.y = fields & Accumulator::FIELD_REL_Y
+            ? mAccumulator.relY * mYScale : 0;
+    pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise.
+    pointerCoords.size = 0;
+    pointerCoords.touchMajor = 0;
+    pointerCoords.touchMinor = 0;
+    pointerCoords.toolMajor = 0;
+    pointerCoords.toolMinor = 0;
+    pointerCoords.orientation = 0;
+
+    float temp;
+    switch (orientation) {
+    case InputReaderPolicyInterface::ROTATION_90:
+        temp = pointerCoords.x;
+        pointerCoords.x = pointerCoords.y;
+        pointerCoords.y = - temp;
+        break;
+
+    case InputReaderPolicyInterface::ROTATION_180:
+        pointerCoords.x = - pointerCoords.x;
+        pointerCoords.y = - pointerCoords.y;
+        break;
+
+    case InputReaderPolicyInterface::ROTATION_270:
+        temp = pointerCoords.x;
+        pointerCoords.x = - pointerCoords.y;
+        pointerCoords.y = temp;
+        break;
+    }
+
+    int32_t metaState = mContext->getGlobalMetaState();
+    getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TRACKBALL, policyFlags,
+            motionEventAction, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            1, & pointerId, & pointerCoords, mXPrecision, mYPrecision, mDownTime);
+}
+
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device, int32_t associatedDisplayId) :
+        InputMapper(device), mAssociatedDisplayId(associatedDisplayId),
+        mSurfaceOrientation(-1), mSurfaceWidth(-1), mSurfaceHeight(-1) {
+    initialize();
+}
+
+TouchInputMapper::~TouchInputMapper() {
+}
+
+uint32_t TouchInputMapper::getSources() {
+    return mAssociatedDisplayId >= 0 ? AINPUT_SOURCE_TOUCHSCREEN : AINPUT_SOURCE_TOUCHPAD;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    // FIXME: Should ensure the surface information is up to date so that orientation changes
+    // are noticed immediately.  Unfortunately we will need to add some extra locks here
+    // to prevent race conditions.
+    // configureSurface();
+
+    info->addMotionRange(AINPUT_MOTION_RANGE_X, mOrientedRanges.x);
+    info->addMotionRange(AINPUT_MOTION_RANGE_Y, mOrientedRanges.y);
+    info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, mOrientedRanges.pressure);
+    info->addMotionRange(AINPUT_MOTION_RANGE_SIZE, mOrientedRanges.size);
+    info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR, mOrientedRanges.touchMajor);
+    info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR, mOrientedRanges.touchMinor);
+    info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR, mOrientedRanges.toolMajor);
+    info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR, mOrientedRanges.toolMinor);
+    info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION, mOrientedRanges.orientation);
+}
+
+void TouchInputMapper::initialize() {
+    mLastTouch.clear();
+    mDownTime = 0;
+    mCurrentVirtualKey.down = false;
+
+    for (uint32_t i = 0; i < MAX_POINTERS; i++) {
+        mAveragingTouchFilter.historyStart[i] = 0;
+        mAveragingTouchFilter.historyEnd[i] = 0;
+    }
+
+    mJumpyTouchFilter.jumpyPointsDropped = 0;
+}
+
+void TouchInputMapper::configure() {
+    InputMapper::configure();
+
+    // Configure basic parameters.
+    mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents();
+    mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents();
+    mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
+
+    // Configure absolute axis information.
+    configureAxes();
+
+    // Configure pressure factors.
+    if (mAxes.pressure.valid) {
+        mPressureOrigin = mAxes.pressure.minValue;
+        mPressureScale = 1.0f / mAxes.pressure.getRange();
+    } else {
+        mPressureOrigin = 0;
+        mPressureScale = 1.0f;
+    }
+
+    mOrientedRanges.pressure.min = 0.0f;
+    mOrientedRanges.pressure.max = 1.0f;
+    mOrientedRanges.pressure.flat = 0.0f;
+    mOrientedRanges.pressure.fuzz = mPressureScale;
+
+    // Configure size factors.
+    if (mAxes.size.valid) {
+        mSizeOrigin = mAxes.size.minValue;
+        mSizeScale = 1.0f / mAxes.size.getRange();
+    } else {
+        mSizeOrigin = 0;
+        mSizeScale = 1.0f;
+    }
+
+    mOrientedRanges.size.min = 0.0f;
+    mOrientedRanges.size.max = 1.0f;
+    mOrientedRanges.size.flat = 0.0f;
+    mOrientedRanges.size.fuzz = mSizeScale;
+
+    // Configure orientation factors.
+    if (mAxes.orientation.valid && mAxes.orientation.maxValue > 0) {
+        mOrientationScale = float(M_PI_2) / mAxes.orientation.maxValue;
+    } else {
+        mOrientationScale = 0.0f;
+    }
+
+    mOrientedRanges.orientation.min = - M_PI_2;
+    mOrientedRanges.orientation.max = M_PI_2;
+    mOrientedRanges.orientation.flat = 0;
+    mOrientedRanges.orientation.fuzz = mOrientationScale;
+
+    // Configure surface dimensions and orientation.
+    configureSurface();
+}
+
+void TouchInputMapper::configureAxes() {
+    mAxes.x.valid = false;
+    mAxes.y.valid = false;
+    mAxes.pressure.valid = false;
+    mAxes.size.valid = false;
+    mAxes.touchMajor.valid = false;
+    mAxes.touchMinor.valid = false;
+    mAxes.toolMajor.valid = false;
+    mAxes.toolMinor.valid = false;
+    mAxes.orientation.valid = false;
+}
+
+bool TouchInputMapper::configureSurface() {
+    // Update orientation and dimensions if needed.
+    int32_t orientation;
+    int32_t width, height;
+    if (mAssociatedDisplayId >= 0) {
+        if (! getPolicy()->getDisplayInfo(mAssociatedDisplayId, & width, & height, & orientation)) {
+            return false;
+        }
+    } else {
+        orientation = InputReaderPolicyInterface::ROTATION_0;
+        width = mAxes.x.getRange();
+        height = mAxes.y.getRange();
+    }
+
+    bool orientationChanged = mSurfaceOrientation != orientation;
+    if (orientationChanged) {
+        mSurfaceOrientation = orientation;
+    }
+
+    bool sizeChanged = mSurfaceWidth != width || mSurfaceHeight != height;
+    if (sizeChanged) {
+        mSurfaceWidth = width;
+        mSurfaceHeight = height;
+
+        // Compute size-dependent translation and scaling factors and place virtual keys.
+        if (mAxes.x.valid && mAxes.y.valid) {
+            mXOrigin = mAxes.x.minValue;
+            mYOrigin = mAxes.y.minValue;
+
+            LOGI("Device configured: id=0x%x, name=%s (display size was changed)",
+                    getDeviceId(), getDeviceName().string());
+
+            mXScale = float(width) / mAxes.x.getRange();
+            mYScale = float(height) / mAxes.y.getRange();
+            mXPrecision = 1.0f / mXScale;
+            mYPrecision = 1.0f / mYScale;
+
+            configureVirtualKeys();
+        } else {
+            mXOrigin = 0;
+            mYOrigin = 0;
+            mXScale = 1.0f;
+            mYScale = 1.0f;
+            mXPrecision = 1.0f;
+            mYPrecision = 1.0f;
+        }
+
+        // Configure touch and tool area ranges.
+        float diagonal = sqrt(float(width * width + height * height));
+        float diagonalFuzz = sqrt(mXScale * mXScale + mYScale * mYScale);
+
+        mOrientedRanges.touchMajor.min = 0.0f;
+        mOrientedRanges.touchMajor.max = diagonal;
+        mOrientedRanges.touchMajor.flat = 0.0f;
+        mOrientedRanges.touchMajor.fuzz = diagonalFuzz;
+        mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
+
+        mOrientedRanges.toolMinor = mOrientedRanges.toolMajor = mOrientedRanges.touchMajor;
+    }
+
+    if (orientationChanged || sizeChanged) {
+        // Compute oriented surface dimensions, precision, and scales.
+        float orientedXScale, orientedYScale;
+        switch (mSurfaceOrientation) {
+        case InputReaderPolicyInterface::ROTATION_90:
+        case InputReaderPolicyInterface::ROTATION_270:
+            mOrientedSurfaceWidth = mSurfaceHeight;
+            mOrientedSurfaceHeight = mSurfaceWidth;
+            mOrientedXPrecision = mYPrecision;
+            mOrientedYPrecision = mXPrecision;
+            orientedXScale = mYScale;
+            orientedYScale = mXScale;
+            break;
+        default:
+            mOrientedSurfaceWidth = mSurfaceWidth;
+            mOrientedSurfaceHeight = mSurfaceHeight;
+            mOrientedXPrecision = mXPrecision;
+            mOrientedYPrecision = mYPrecision;
+            orientedXScale = mXScale;
+            orientedYScale = mYScale;
+            break;
+        }
+
+        // Configure position ranges.
+        mOrientedRanges.x.min = 0;
+        mOrientedRanges.x.max = mOrientedSurfaceWidth;
+        mOrientedRanges.x.flat = 0;
+        mOrientedRanges.x.fuzz = orientedXScale;
+
+        mOrientedRanges.y.min = 0;
+        mOrientedRanges.y.max = mOrientedSurfaceHeight;
+        mOrientedRanges.y.flat = 0;
+        mOrientedRanges.y.fuzz = orientedYScale;
+    }
+
+    return true;
+}
+
+void TouchInputMapper::configureVirtualKeys() {
+    assert(mAxes.x.valid && mAxes.y.valid);
+
+    Vector<InputReaderPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions;
+    getPolicy()->getVirtualKeyDefinitions(getDeviceName(), virtualKeyDefinitions);
+
+    { // acquire virtual key lock
+        AutoMutex _l(mVirtualKeyLock);
+
+        mVirtualKeys.clear();
+
+        if (virtualKeyDefinitions.size() == 0) {
+            return;
+        }
+
+        mVirtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+        int32_t touchScreenLeft = mAxes.x.minValue;
+        int32_t touchScreenTop = mAxes.y.minValue;
+        int32_t touchScreenWidth = mAxes.x.getRange();
+        int32_t touchScreenHeight = mAxes.y.getRange();
+
+        for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+            const InputReaderPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition =
+                    virtualKeyDefinitions[i];
+
+            mVirtualKeys.add();
+            VirtualKey& virtualKey = mVirtualKeys.editTop();
+
+            virtualKey.scanCode = virtualKeyDefinition.scanCode;
+            int32_t keyCode;
+            uint32_t flags;
+            if (getEventHub()->scancodeToKeycode(getDeviceId(), virtualKey.scanCode,
+                    & keyCode, & flags)) {
+                LOGW("  VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
+                mVirtualKeys.pop(); // drop the key
+                continue;
+            }
+
+            virtualKey.keyCode = keyCode;
+            virtualKey.flags = flags;
+
+            // convert the key definition's display coordinates into touch coordinates for a hit box
+            int32_t halfWidth = virtualKeyDefinition.width / 2;
+            int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+            virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
+                    * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+            virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+                    * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+            virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+                    * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+            virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+                    * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+
+            LOGI("  VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
+                    virtualKey.scanCode, virtualKey.keyCode,
+                    virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
+        }
+    } // release virtual key lock
+}
+
+void TouchInputMapper::reset() {
+    // Synthesize touch up event if touch is currently down.
+    // This will also take care of finishing virtual key processing if needed.
+    if (mLastTouch.pointerCount != 0) {
+        nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+        mCurrentTouch.clear();
+        syncTouch(when, true);
+    }
+
+    InputMapper::reset();
+
+    // Reinitialize.
+    initialize();
+}
+
+void TouchInputMapper::syncTouch(nsecs_t when, bool havePointerIds) {
+    /* Refresh associated display information and update our size configuration if needed. */
+
+    if (! configureSurface()) {
+        return;
+    }
+
+    /* Apply policy */
 
     uint32_t policyFlags = 0;
-    if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
-        device->touchScreen.lastTouch.clear();
+    int32_t policyActions = getPolicy()->interceptGeneric(when, policyFlags);
+
+    if (! applyStandardPolicyActions(when, policyActions)) {
+        mLastTouch.clear();
         return; // event dropped
     }
 
     /* Preprocess pointer data */
 
-    if (device->touchScreen.parameters.useBadTouchFilter) {
-        if (device->touchScreen.applyBadTouchFilter()) {
+    if (mParameters.useBadTouchFilter) {
+        if (applyBadTouchFilter()) {
             havePointerIds = false;
         }
     }
 
-    if (device->touchScreen.parameters.useJumpyTouchFilter) {
-        if (device->touchScreen.applyJumpyTouchFilter()) {
+    if (mParameters.useJumpyTouchFilter) {
+        if (applyJumpyTouchFilter()) {
             havePointerIds = false;
         }
     }
 
     if (! havePointerIds) {
-        device->touchScreen.calculatePointerIds();
+        calculatePointerIds();
     }
 
-    InputDevice::TouchData temp;
-    InputDevice::TouchData* savedTouch;
-    if (device->touchScreen.parameters.useAveragingTouchFilter) {
-        temp.copyFrom(device->touchScreen.currentTouch);
+    TouchData temp;
+    TouchData* savedTouch;
+    if (mParameters.useAveragingTouchFilter) {
+        temp.copyFrom(mCurrentTouch);
         savedTouch = & temp;
 
-        device->touchScreen.applyAveragingTouchFilter();
+        applyAveragingTouchFilter();
     } else {
-        savedTouch = & device->touchScreen.currentTouch;
+        savedTouch = & mCurrentTouch;
     }
 
-    /* Process virtual keys or touches */
+    /* Process touches and virtual keys */
 
-    if (! consumeVirtualKeyTouches(when, device, policyFlags)) {
-        dispatchTouches(when, device, policyFlags);
+    TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
+    if (touchResult == DISPATCH_TOUCH) {
+        dispatchTouches(when, policyFlags);
     }
 
-    // Copy current touch to last touch in preparation for the next cycle.
-    device->touchScreen.lastTouch.copyFrom(*savedTouch);
-}
+    /* Copy current touch to last touch in preparation for the next cycle. */
 
-bool InputReader::consumeVirtualKeyTouches(nsecs_t when,
-        InputDevice* device, uint32_t policyFlags) {
-    switch (device->touchScreen.currentVirtualKey.status) {
-    case InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_CANCELED:
-        if (device->touchScreen.currentTouch.pointerCount == 0) {
-            // Pointer went up after virtual key canceled.
-            device->touchScreen.currentVirtualKey.status =
-                    InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_UP;
-        }
-        return true; // consumed
-
-    case InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_DOWN:
-        if (device->touchScreen.currentTouch.pointerCount == 0) {
-            // Pointer went up while virtual key was down.
-            device->touchScreen.currentVirtualKey.status =
-                    InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_UP;
-#if DEBUG_VIRTUAL_KEYS
-            LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
-                    device->touchScreen.currentVirtualKey.keyCode,
-                    device->touchScreen.currentVirtualKey.scanCode);
-#endif
-            dispatchVirtualKey(when, device, policyFlags, AKEY_EVENT_ACTION_UP,
-                    AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
-            return true; // consumed
-        }
-
-        if (device->touchScreen.currentTouch.pointerCount == 1) {
-            const InputDevice::VirtualKey* virtualKey = device->touchScreen.findVirtualKeyHit();
-            if (virtualKey
-                    && virtualKey->keyCode == device->touchScreen.currentVirtualKey.keyCode) {
-                // Pointer is still within the space of the virtual key.
-                return true; // consumed
-            }
-        }
-
-        // Pointer left virtual key area or another pointer also went down.
-        // Send key cancellation.
-        device->touchScreen.currentVirtualKey.status =
-                InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_CANCELED;
-#if DEBUG_VIRTUAL_KEYS
-        LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
-                device->touchScreen.currentVirtualKey.keyCode,
-                device->touchScreen.currentVirtualKey.scanCode);
-#endif
-        dispatchVirtualKey(when, device, policyFlags, AKEY_EVENT_ACTION_UP,
-                AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
-                        | AKEY_EVENT_FLAG_CANCELED);
-        return true; // consumed
-
-    default:
-        if (device->touchScreen.currentTouch.pointerCount == 1
-                && device->touchScreen.lastTouch.pointerCount == 0) {
-            // Pointer just went down.  Check for virtual key hit.
-            const InputDevice::VirtualKey* virtualKey = device->touchScreen.findVirtualKeyHit();
-            if (virtualKey) {
-                device->touchScreen.currentVirtualKey.status =
-                        InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_DOWN;
-                device->touchScreen.currentVirtualKey.downTime = when;
-                device->touchScreen.currentVirtualKey.keyCode = virtualKey->keyCode;
-                device->touchScreen.currentVirtualKey.scanCode = virtualKey->scanCode;
-#if DEBUG_VIRTUAL_KEYS
-                LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
-                        device->touchScreen.currentVirtualKey.keyCode,
-                        device->touchScreen.currentVirtualKey.scanCode);
-#endif
-                dispatchVirtualKey(when, device, policyFlags, AKEY_EVENT_ACTION_DOWN,
-                        AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
-                return true; // consumed
-            }
-        }
-        return false; // not consumed
+    if (touchResult == DROP_STROKE) {
+        mLastTouch.clear();
+    } else {
+        mLastTouch.copyFrom(*savedTouch);
     }
 }
 
-void InputReader::dispatchVirtualKey(nsecs_t when,
-        InputDevice* device, uint32_t policyFlags,
-        int32_t keyEventAction, int32_t keyEventFlags) {
-    updateExportedVirtualKeyState();
+TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
+        nsecs_t when, uint32_t policyFlags) {
+    int32_t keyEventAction, keyEventFlags;
+    int32_t keyCode, scanCode, downTime;
+    TouchResult touchResult;
 
-    int32_t keyCode = device->touchScreen.currentVirtualKey.keyCode;
-    int32_t scanCode = device->touchScreen.currentVirtualKey.scanCode;
-    nsecs_t downTime = device->touchScreen.currentVirtualKey.downTime;
-    int32_t metaState = globalMetaState();
+    { // acquire virtual key lock
+        AutoMutex _l(mVirtualKeyLock);
+
+        if (mCurrentVirtualKey.down) {
+            if (mCurrentTouch.pointerCount == 0) {
+                // Pointer went up while virtual key was down.
+                mCurrentVirtualKey.down = false;
+#if DEBUG_VIRTUAL_KEYS
+                LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+                        mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+                keyEventAction = AKEY_EVENT_ACTION_UP;
+                keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+                touchResult = SKIP_TOUCH;
+                goto DispatchVirtualKey;
+            }
+
+            if (mCurrentTouch.pointerCount == 1) {
+                int32_t x = mCurrentTouch.pointers[0].x;
+                int32_t y = mCurrentTouch.pointers[0].y;
+                const VirtualKey* virtualKey = findVirtualKeyHitLvk(x, y);
+                if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) {
+                    // Pointer is still within the space of the virtual key.
+                    return SKIP_TOUCH;
+                }
+            }
+
+            // Pointer left virtual key area or another pointer also went down.
+            // Send key cancellation and drop the stroke so subsequent motions will be
+            // considered fresh downs.  This is useful when the user swipes away from the
+            // virtual key area into the main display surface.
+            mCurrentVirtualKey.down = false;
+#if DEBUG_VIRTUAL_KEYS
+            LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+                    mCurrentVirtualKey.keyCode, mCurrentVirtualKey.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;
+        } else {
+            if (mCurrentTouch.pointerCount >= 1 && mLastTouch.pointerCount == 0) {
+                // Pointer just went down.  Handle off-screen touches, if needed.
+                int32_t x = mCurrentTouch.pointers[0].x;
+                int32_t y = mCurrentTouch.pointers[0].y;
+                if (! isPointInsideSurface(x, y)) {
+                    // If exactly one pointer went down, check for virtual key hit.
+                    // Otherwise we will drop the entire stroke.
+                    if (mCurrentTouch.pointerCount == 1) {
+                        const VirtualKey* virtualKey = findVirtualKeyHitLvk(x, y);
+                        if (virtualKey) {
+                            mCurrentVirtualKey.down = true;
+                            mCurrentVirtualKey.downTime = when;
+                            mCurrentVirtualKey.keyCode = virtualKey->keyCode;
+                            mCurrentVirtualKey.scanCode = virtualKey->scanCode;
+#if DEBUG_VIRTUAL_KEYS
+                            LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+                                    mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+                            keyEventAction = AKEY_EVENT_ACTION_DOWN;
+                            keyEventFlags = AKEY_EVENT_FLAG_FROM_SYSTEM
+                                    | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+                            touchResult = SKIP_TOUCH;
+                            goto DispatchVirtualKey;
+                        }
+                    }
+                    return DROP_STROKE;
+                }
+            }
+            return DISPATCH_TOUCH;
+        }
+
+    DispatchVirtualKey:
+        // Collect remaining state needed to dispatch virtual key.
+        keyCode = mCurrentVirtualKey.keyCode;
+        scanCode = mCurrentVirtualKey.scanCode;
+        downTime = mCurrentVirtualKey.downTime;
+    } // release virtual key lock
+
+    // Dispatch virtual key.
+    int32_t metaState = mContext->getGlobalMetaState();
 
     if (keyEventAction == AKEY_EVENT_ACTION_DOWN) {
-        mPolicy->virtualKeyDownFeedback();
+        getPolicy()->virtualKeyDownFeedback();
     }
 
-    int32_t policyActions = mPolicy->interceptKey(when, device->id,
+    int32_t policyActions = getPolicy()->interceptKey(when, getDeviceId(),
             keyEventAction == AKEY_EVENT_ACTION_DOWN, keyCode, scanCode, policyFlags);
 
-    if (applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
-        mDispatcher->notifyKey(when, device->id, AINPUT_SOURCE_KEYBOARD, policyFlags,
+    if (applyStandardPolicyActions(when, policyActions)) {
+        getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
                 keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
     }
+    return touchResult;
 }
 
-void InputReader::dispatchTouches(nsecs_t when,
-        InputDevice* device, uint32_t policyFlags) {
-    uint32_t currentPointerCount = device->touchScreen.currentTouch.pointerCount;
-    uint32_t lastPointerCount = device->touchScreen.lastTouch.pointerCount;
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+    uint32_t currentPointerCount = mCurrentTouch.pointerCount;
+    uint32_t lastPointerCount = mLastTouch.pointerCount;
     if (currentPointerCount == 0 && lastPointerCount == 0) {
         return; // nothing to do!
     }
 
-    BitSet32 currentIdBits = device->touchScreen.currentTouch.idBits;
-    BitSet32 lastIdBits = device->touchScreen.lastTouch.idBits;
+    BitSet32 currentIdBits = mCurrentTouch.idBits;
+    BitSet32 lastIdBits = mLastTouch.idBits;
 
     if (currentIdBits == lastIdBits) {
         // No pointer id changes so this is a move event.
         // The dispatcher takes care of batching moves so we don't have to deal with that here.
         int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE;
-        dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch,
+        dispatchTouch(when, policyFlags, & mCurrentTouch,
                 currentIdBits, -1, motionEventAction);
     } else {
         // There may be pointers going up and pointers going down at the same time when pointer
@@ -791,7 +1536,7 @@
                 motionEventAction = AMOTION_EVENT_ACTION_POINTER_UP;
             }
 
-            dispatchTouch(when, device, policyFlags, & device->touchScreen.lastTouch,
+            dispatchTouch(when, policyFlags, & mLastTouch,
                     oldActiveIdBits, upId, motionEventAction);
         }
 
@@ -804,40 +1549,24 @@
             int32_t motionEventAction;
             if (oldActiveIdBits.isEmpty()) {
                 motionEventAction = AMOTION_EVENT_ACTION_DOWN;
-                device->touchScreen.downTime = when;
+                mDownTime = when;
             } else {
                 motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN;
             }
 
-            dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch,
+            dispatchTouch(when, policyFlags, & mCurrentTouch,
                     activeIdBits, downId, motionEventAction);
         }
     }
 }
 
-void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags,
-        InputDevice::TouchData* touch, BitSet32 idBits, uint32_t changedId,
+void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,
+        TouchData* touch, BitSet32 idBits, uint32_t changedId,
         int32_t motionEventAction) {
-    int32_t orientedWidth, orientedHeight;
-    switch (mDisplayOrientation) {
-    case InputReaderPolicyInterface::ROTATION_90:
-    case InputReaderPolicyInterface::ROTATION_270:
-        orientedWidth = mDisplayHeight;
-        orientedHeight = mDisplayWidth;
-        break;
-    default:
-        orientedWidth = mDisplayWidth;
-        orientedHeight = mDisplayHeight;
-        break;
-    }
-
     uint32_t pointerCount = 0;
     int32_t pointerIds[MAX_POINTERS];
     PointerCoords pointerCoords[MAX_POINTERS];
 
-    const InputDevice::TouchScreenState::Precalculated& precalculated =
-            device->touchScreen.precalculated;
-
     // Walk through the the active pointers and map touch screen coordinates (TouchData) into
     // display coordinates (PointerCoords) and adjust for display orientation.
     while (! idBits.isEmpty()) {
@@ -845,55 +1574,57 @@
         idBits.clearBit(id);
         uint32_t index = touch->idToIndex[id];
 
-        float x = float(touch->pointers[index].x
-                - precalculated.xOrigin) * precalculated.xScale;
-        float y = float(touch->pointers[index].y
-                - precalculated.yOrigin) * precalculated.yScale;
-        float pressure = float(touch->pointers[index].pressure
-                - precalculated.pressureOrigin) * precalculated.pressureScale;
-        float size = float(touch->pointers[index].size
-                - precalculated.sizeOrigin) * precalculated.sizeScale;
+        float x = float(touch->pointers[index].x - mXOrigin) * mXScale;
+        float y = float(touch->pointers[index].y - mYOrigin) * mYScale;
+        float pressure = float(touch->pointers[index].pressure - mPressureOrigin) * mPressureScale;
+        float size = float(touch->pointers[index].size - mSizeOrigin) * mSizeScale;
 
-        float orientation = float(touch->pointers[index].orientation)
-                * precalculated.orientationScale;
+        float orientation = float(touch->pointers[index].orientation) * mOrientationScale;
 
-        bool vertical = abs(orientation) <= M_PI / 8;
+        float touchMajor, touchMinor, toolMajor, toolMinor;
+        if (abs(orientation) <= M_PI_4) {
+            // Nominally vertical orientation: scale major axis by Y, and scale minor axis by X.
+            touchMajor = float(touch->pointers[index].touchMajor) * mYScale;
+            touchMinor = float(touch->pointers[index].touchMinor) * mXScale;
+            toolMajor = float(touch->pointers[index].toolMajor) * mYScale;
+            toolMinor = float(touch->pointers[index].toolMinor) * mXScale;
+        } else {
+            // Nominally horizontal orientation: scale major axis by X, and scale minor axis by Y.
+            touchMajor = float(touch->pointers[index].touchMajor) * mXScale;
+            touchMinor = float(touch->pointers[index].touchMinor) * mYScale;
+            toolMajor = float(touch->pointers[index].toolMajor) * mXScale;
+            toolMinor = float(touch->pointers[index].toolMinor) * mYScale;
+        }
 
-        switch (mDisplayOrientation) {
+        switch (mSurfaceOrientation) {
         case InputReaderPolicyInterface::ROTATION_90: {
             float xTemp = x;
             x = y;
-            y = mDisplayWidth - xTemp;
-            vertical = ! vertical;
+            y = mOrientedSurfaceWidth - xTemp;
+            orientation -= M_PI_2;
+            if (orientation < - M_PI_2) {
+                orientation += M_PI;
+            }
             break;
         }
         case InputReaderPolicyInterface::ROTATION_180: {
-            x = mDisplayWidth - x;
-            y = mDisplayHeight - y;
+            x = mOrientedSurfaceWidth - x;
+            y = mOrientedSurfaceHeight - y;
+            orientation = - orientation;
             break;
         }
         case InputReaderPolicyInterface::ROTATION_270: {
             float xTemp = x;
-            x = mDisplayHeight - y;
+            x = mOrientedSurfaceHeight - y;
             y = xTemp;
-            vertical = ! vertical;
+            orientation += M_PI_2;
+            if (orientation > M_PI_2) {
+                orientation -= M_PI;
+            }
             break;
         }
         }
 
-        float touchMajor, touchMinor, toolMajor, toolMinor;
-        if (vertical) {
-            touchMajor = float(touch->pointers[index].touchMajor) * precalculated.yScale;
-            touchMinor = float(touch->pointers[index].touchMinor) * precalculated.xScale;
-            toolMajor = float(touch->pointers[index].toolMajor) * precalculated.yScale;
-            toolMinor = float(touch->pointers[index].toolMinor) * precalculated.xScale;
-        } else {
-            touchMajor = float(touch->pointers[index].touchMajor) * precalculated.xScale;
-            touchMinor = float(touch->pointers[index].touchMinor) * precalculated.yScale;
-            toolMajor = float(touch->pointers[index].toolMajor) * precalculated.xScale;
-            toolMinor = float(touch->pointers[index].toolMinor) * precalculated.yScale;
-        }
-
         pointerIds[pointerCount] = int32_t(id);
 
         pointerCoords[pointerCount].x = x;
@@ -919,561 +1650,984 @@
     if (motionEventAction == AMOTION_EVENT_ACTION_DOWN) {
         if (pointerCoords[0].x <= 0) {
             motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_LEFT;
-        } else if (pointerCoords[0].x >= orientedWidth) {
+        } else if (pointerCoords[0].x >= mOrientedSurfaceWidth) {
             motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_RIGHT;
         }
         if (pointerCoords[0].y <= 0) {
             motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_TOP;
-        } else if (pointerCoords[0].y >= orientedHeight) {
+        } else if (pointerCoords[0].y >= mOrientedSurfaceHeight) {
             motionEventEdgeFlags |= AMOTION_EVENT_EDGE_FLAG_BOTTOM;
         }
     }
 
-    nsecs_t downTime = device->touchScreen.downTime;
-    mDispatcher->notifyMotion(when, device->id, AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
-            motionEventAction, globalMetaState(), motionEventEdgeFlags,
+    getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_TOUCHSCREEN, policyFlags,
+            motionEventAction, getContext()->getGlobalMetaState(), motionEventEdgeFlags,
             pointerCount, pointerIds, pointerCoords,
-            0, 0, downTime);
+            mOrientedXPrecision, mOrientedYPrecision, mDownTime);
 }
 
-void InputReader::onTrackballStateChanged(nsecs_t when,
-        InputDevice* device) {
-    static const uint32_t DELTA_FIELDS =
-            InputDevice::TrackballState::Accumulator::FIELD_REL_X
-            | InputDevice::TrackballState::Accumulator::FIELD_REL_Y;
-
-    /* Refresh display properties so we can trackball moves according to display orientation */
-
-    if (! refreshDisplayProperties()) {
-        return;
+bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) {
+    if (mAxes.x.valid && mAxes.y.valid) {
+        return x >= mAxes.x.minValue && x <= mAxes.x.maxValue
+                && y >= mAxes.y.minValue && y <= mAxes.y.maxValue;
     }
+    return true;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHitLvk(int32_t x, int32_t y) {
+    for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+        const VirtualKey& virtualKey = mVirtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+        LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+                "left=%d, top=%d, right=%d, bottom=%d",
+                x, y,
+                virtualKey.keyCode, virtualKey.scanCode,
+                virtualKey.hitLeft, virtualKey.hitTop,
+                virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+        if (virtualKey.isHit(x, y)) {
+            return & virtualKey;
+        }
+    }
+
+    return NULL;
+}
+
+void TouchInputMapper::calculatePointerIds() {
+    uint32_t currentPointerCount = mCurrentTouch.pointerCount;
+    uint32_t lastPointerCount = mLastTouch.pointerCount;
+
+    if (currentPointerCount == 0) {
+        // No pointers to assign.
+        mCurrentTouch.idBits.clear();
+    } else if (lastPointerCount == 0) {
+        // All pointers are new.
+        mCurrentTouch.idBits.clear();
+        for (uint32_t i = 0; i < currentPointerCount; i++) {
+            mCurrentTouch.pointers[i].id = i;
+            mCurrentTouch.idToIndex[i] = i;
+            mCurrentTouch.idBits.markBit(i);
+        }
+    } else if (currentPointerCount == 1 && lastPointerCount == 1) {
+        // Only one pointer and no change in count so it must have the same id as before.
+        uint32_t id = mLastTouch.pointers[0].id;
+        mCurrentTouch.pointers[0].id = id;
+        mCurrentTouch.idToIndex[id] = 0;
+        mCurrentTouch.idBits.value = BitSet32::valueForBit(id);
+    } else {
+        // General case.
+        // We build a heap of squared euclidean distances between current and last pointers
+        // associated with the current and last pointer indices.  Then, we find the best
+        // match (by distance) for each current pointer.
+        PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
+
+        uint32_t heapSize = 0;
+        for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+                currentPointerIndex++) {
+            for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+                    lastPointerIndex++) {
+                int64_t deltaX = mCurrentTouch.pointers[currentPointerIndex].x
+                        - mLastTouch.pointers[lastPointerIndex].x;
+                int64_t deltaY = mCurrentTouch.pointers[currentPointerIndex].y
+                        - mLastTouch.pointers[lastPointerIndex].y;
+
+                uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+                // Insert new element into the heap (sift up).
+                heap[heapSize].currentPointerIndex = currentPointerIndex;
+                heap[heapSize].lastPointerIndex = lastPointerIndex;
+                heap[heapSize].distance = distance;
+                heapSize += 1;
+            }
+        }
+
+        // Heapify
+        for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
+            startIndex -= 1;
+            for (uint32_t parentIndex = startIndex; ;) {
+                uint32_t childIndex = parentIndex * 2 + 1;
+                if (childIndex >= heapSize) {
+                    break;
+                }
+
+                if (childIndex + 1 < heapSize
+                        && heap[childIndex + 1].distance < heap[childIndex].distance) {
+                    childIndex += 1;
+                }
+
+                if (heap[parentIndex].distance <= heap[childIndex].distance) {
+                    break;
+                }
+
+                swap(heap[parentIndex], heap[childIndex]);
+                parentIndex = childIndex;
+            }
+        }
+
+#if DEBUG_POINTER_ASSIGNMENT
+        LOGD("calculatePointerIds - initial distance min-heap: size=%d", heapSize);
+        for (size_t i = 0; i < heapSize; i++) {
+            LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
+                    i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+                    heap[i].distance);
+        }
+#endif
+
+        // Pull matches out by increasing order of distance.
+        // To avoid reassigning pointers that have already been matched, the loop keeps track
+        // of which last and current pointers have been matched using the matchedXXXBits variables.
+        // It also tracks the used pointer id bits.
+        BitSet32 matchedLastBits(0);
+        BitSet32 matchedCurrentBits(0);
+        BitSet32 usedIdBits(0);
+        bool first = true;
+        for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) {
+            for (;;) {
+                if (first) {
+                    // The first time through the loop, we just consume the root element of
+                    // the heap (the one with smallest distance).
+                    first = false;
+                } else {
+                    // Previous iterations consumed the root element of the heap.
+                    // Pop root element off of the heap (sift down).
+                    heapSize -= 1;
+                    assert(heapSize > 0);
+
+                    // Sift down.
+                    heap[0] = heap[heapSize];
+                    for (uint32_t parentIndex = 0; ;) {
+                        uint32_t childIndex = parentIndex * 2 + 1;
+                        if (childIndex >= heapSize) {
+                            break;
+                        }
+
+                        if (childIndex + 1 < heapSize
+                                && heap[childIndex + 1].distance < heap[childIndex].distance) {
+                            childIndex += 1;
+                        }
+
+                        if (heap[parentIndex].distance <= heap[childIndex].distance) {
+                            break;
+                        }
+
+                        swap(heap[parentIndex], heap[childIndex]);
+                        parentIndex = childIndex;
+                    }
+
+#if DEBUG_POINTER_ASSIGNMENT
+                    LOGD("calculatePointerIds - reduced distance min-heap: size=%d", heapSize);
+                    for (size_t i = 0; i < heapSize; i++) {
+                        LOGD("  heap[%d]: cur=%d, last=%d, distance=%lld",
+                                i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+                                heap[i].distance);
+                    }
+#endif
+                }
+
+                uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+                if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+                uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+                if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+                matchedCurrentBits.markBit(currentPointerIndex);
+                matchedLastBits.markBit(lastPointerIndex);
+
+                uint32_t id = mLastTouch.pointers[lastPointerIndex].id;
+                mCurrentTouch.pointers[currentPointerIndex].id = id;
+                mCurrentTouch.idToIndex[id] = currentPointerIndex;
+                usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+                LOGD("calculatePointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+                        lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+                break;
+            }
+        }
+
+        // Assign fresh ids to new pointers.
+        if (currentPointerCount > lastPointerCount) {
+            for (uint32_t i = currentPointerCount - lastPointerCount; ;) {
+                uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit();
+                uint32_t id = usedIdBits.firstUnmarkedBit();
+
+                mCurrentTouch.pointers[currentPointerIndex].id = id;
+                mCurrentTouch.idToIndex[id] = currentPointerIndex;
+                usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+                LOGD("calculatePointerIds - assigned: cur=%d, id=%d",
+                        currentPointerIndex, id);
+#endif
+
+                if (--i == 0) break; // done
+                matchedCurrentBits.markBit(currentPointerIndex);
+            }
+        }
+
+        // Fix id bits.
+        mCurrentTouch.idBits = usedIdBits;
+    }
+}
+
+/* Special hack for devices that have bad screen data: if one of the
+ * points has moved more than a screen height from the last position,
+ * then drop it. */
+bool TouchInputMapper::applyBadTouchFilter() {
+    // This hack requires valid axis parameters.
+    if (! mAxes.y.valid) {
+        return false;
+    }
+
+    uint32_t pointerCount = mCurrentTouch.pointerCount;
+
+    // Nothing to do if there are no points.
+    if (pointerCount == 0) {
+        return false;
+    }
+
+    // Don't do anything if a finger is going down or up.  We run
+    // here before assigning pointer IDs, so there isn't a good
+    // way to do per-finger matching.
+    if (pointerCount != mLastTouch.pointerCount) {
+        return false;
+    }
+
+    // We consider a single movement across more than a 7/16 of
+    // the long size of the screen to be bad.  This was a magic value
+    // determined by looking at the maximum distance it is feasible
+    // to actually move in one sample.
+    int32_t maxDeltaY = mAxes.y.getRange() * 7 / 16;
+
+    // XXX The original code in InputDevice.java included commented out
+    //     code for testing the X axis.  Note that when we drop a point
+    //     we don't actually restore the old X either.  Strange.
+    //     The old code also tries to track when bad points were previously
+    //     detected but it turns out that due to the placement of a "break"
+    //     at the end of the loop, we never set mDroppedBadPoint to true
+    //     so it is effectively dead code.
+    // Need to figure out if the old code is busted or just overcomplicated
+    // but working as intended.
+
+    // Look through all new points and see if any are farther than
+    // acceptable from all previous points.
+    for (uint32_t i = pointerCount; i-- > 0; ) {
+        int32_t y = mCurrentTouch.pointers[i].y;
+        int32_t closestY = INT_MAX;
+        int32_t closestDeltaY = 0;
+
+#if DEBUG_HACKS
+        LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y);
+#endif
+
+        for (uint32_t j = pointerCount; j-- > 0; ) {
+            int32_t lastY = mLastTouch.pointers[j].y;
+            int32_t deltaY = abs(y - lastY);
+
+#if DEBUG_HACKS
+            LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d",
+                    j, lastY, deltaY);
+#endif
+
+            if (deltaY < maxDeltaY) {
+                goto SkipSufficientlyClosePoint;
+            }
+            if (deltaY < closestDeltaY) {
+                closestDeltaY = deltaY;
+                closestY = lastY;
+            }
+        }
+
+        // Must not have found a close enough match.
+#if DEBUG_HACKS
+        LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d",
+                i, y, closestY, closestDeltaY, maxDeltaY);
+#endif
+
+        mCurrentTouch.pointers[i].y = closestY;
+        return true; // XXX original code only corrects one point
+
+    SkipSufficientlyClosePoint: ;
+    }
+
+    // No change.
+    return false;
+}
+
+/* Special hack for devices that have bad screen data: drop points where
+ * the coordinate value for one axis has jumped to the other pointer's location.
+ */
+bool TouchInputMapper::applyJumpyTouchFilter() {
+    // This hack requires valid axis parameters.
+    if (! mAxes.y.valid) {
+        return false;
+    }
+
+    uint32_t pointerCount = mCurrentTouch.pointerCount;
+    if (mLastTouch.pointerCount != pointerCount) {
+#if DEBUG_HACKS
+        LOGD("JumpyTouchFilter: Different pointer count %d -> %d",
+                mLastTouch.pointerCount, pointerCount);
+        for (uint32_t i = 0; i < pointerCount; i++) {
+            LOGD("  Pointer %d (%d, %d)", i,
+                    mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y);
+        }
+#endif
+
+        if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
+            if (mLastTouch.pointerCount == 1 && pointerCount == 2) {
+                // Just drop the first few events going from 1 to 2 pointers.
+                // They're bad often enough that they're not worth considering.
+                mCurrentTouch.pointerCount = 1;
+                mJumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+                LOGD("JumpyTouchFilter: Pointer 2 dropped");
+#endif
+                return true;
+            } else if (mLastTouch.pointerCount == 2 && pointerCount == 1) {
+                // The event when we go from 2 -> 1 tends to be messed up too
+                mCurrentTouch.pointerCount = 2;
+                mCurrentTouch.pointers[0] = mLastTouch.pointers[0];
+                mCurrentTouch.pointers[1] = mLastTouch.pointers[1];
+                mJumpyTouchFilter.jumpyPointsDropped += 1;
+
+#if DEBUG_HACKS
+                for (int32_t i = 0; i < 2; i++) {
+                    LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i,
+                            mCurrentTouch.pointers[i].x, mCurrentTouch.pointers[i].y);
+                }
+#endif
+                return true;
+            }
+        }
+        // Reset jumpy points dropped on other transitions or if limit exceeded.
+        mJumpyTouchFilter.jumpyPointsDropped = 0;
+
+#if DEBUG_HACKS
+        LOGD("JumpyTouchFilter: Transition - drop limit reset");
+#endif
+        return false;
+    }
+
+    // We have the same number of pointers as last time.
+    // A 'jumpy' point is one where the coordinate value for one axis
+    // has jumped to the other pointer's location. No need to do anything
+    // else if we only have one pointer.
+    if (pointerCount < 2) {
+        return false;
+    }
+
+    if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
+        int jumpyEpsilon = mAxes.y.getRange() / JUMPY_EPSILON_DIVISOR;
+
+        // We only replace the single worst jumpy point as characterized by pointer distance
+        // in a single axis.
+        int32_t badPointerIndex = -1;
+        int32_t badPointerReplacementIndex = -1;
+        int32_t badPointerDistance = INT_MIN; // distance to be corrected
+
+        for (uint32_t i = pointerCount; i-- > 0; ) {
+            int32_t x = mCurrentTouch.pointers[i].x;
+            int32_t y = mCurrentTouch.pointers[i].y;
+
+#if DEBUG_HACKS
+            LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y);
+#endif
+
+            // Check if a touch point is too close to another's coordinates
+            bool dropX = false, dropY = false;
+            for (uint32_t j = 0; j < pointerCount; j++) {
+                if (i == j) {
+                    continue;
+                }
+
+                if (abs(x - mCurrentTouch.pointers[j].x) <= jumpyEpsilon) {
+                    dropX = true;
+                    break;
+                }
+
+                if (abs(y - mCurrentTouch.pointers[j].y) <= jumpyEpsilon) {
+                    dropY = true;
+                    break;
+                }
+            }
+            if (! dropX && ! dropY) {
+                continue; // not jumpy
+            }
+
+            // Find a replacement candidate by comparing with older points on the
+            // complementary (non-jumpy) axis.
+            int32_t distance = INT_MIN; // distance to be corrected
+            int32_t replacementIndex = -1;
+
+            if (dropX) {
+                // X looks too close.  Find an older replacement point with a close Y.
+                int32_t smallestDeltaY = INT_MAX;
+                for (uint32_t j = 0; j < pointerCount; j++) {
+                    int32_t deltaY = abs(y - mLastTouch.pointers[j].y);
+                    if (deltaY < smallestDeltaY) {
+                        smallestDeltaY = deltaY;
+                        replacementIndex = j;
+                    }
+                }
+                distance = abs(x - mLastTouch.pointers[replacementIndex].x);
+            } else {
+                // Y looks too close.  Find an older replacement point with a close X.
+                int32_t smallestDeltaX = INT_MAX;
+                for (uint32_t j = 0; j < pointerCount; j++) {
+                    int32_t deltaX = abs(x - mLastTouch.pointers[j].x);
+                    if (deltaX < smallestDeltaX) {
+                        smallestDeltaX = deltaX;
+                        replacementIndex = j;
+                    }
+                }
+                distance = abs(y - mLastTouch.pointers[replacementIndex].y);
+            }
+
+            // If replacing this pointer would correct a worse error than the previous ones
+            // considered, then use this replacement instead.
+            if (distance > badPointerDistance) {
+                badPointerIndex = i;
+                badPointerReplacementIndex = replacementIndex;
+                badPointerDistance = distance;
+            }
+        }
+
+        // Correct the jumpy pointer if one was found.
+        if (badPointerIndex >= 0) {
+#if DEBUG_HACKS
+            LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)",
+                    badPointerIndex,
+                    mLastTouch.pointers[badPointerReplacementIndex].x,
+                    mLastTouch.pointers[badPointerReplacementIndex].y);
+#endif
+
+            mCurrentTouch.pointers[badPointerIndex].x =
+                    mLastTouch.pointers[badPointerReplacementIndex].x;
+            mCurrentTouch.pointers[badPointerIndex].y =
+                    mLastTouch.pointers[badPointerReplacementIndex].y;
+            mJumpyTouchFilter.jumpyPointsDropped += 1;
+            return true;
+        }
+    }
+
+    mJumpyTouchFilter.jumpyPointsDropped = 0;
+    return false;
+}
+
+/* Special hack for devices that have bad screen data: aggregate and
+ * compute averages of the coordinate data, to reduce the amount of
+ * jitter seen by applications. */
+void TouchInputMapper::applyAveragingTouchFilter() {
+    for (uint32_t currentIndex = 0; currentIndex < mCurrentTouch.pointerCount; currentIndex++) {
+        uint32_t id = mCurrentTouch.pointers[currentIndex].id;
+        int32_t x = mCurrentTouch.pointers[currentIndex].x;
+        int32_t y = mCurrentTouch.pointers[currentIndex].y;
+        int32_t pressure = mCurrentTouch.pointers[currentIndex].pressure;
+
+        if (mLastTouch.idBits.hasBit(id)) {
+            // Pointer was down before and is still down now.
+            // Compute average over history trace.
+            uint32_t start = mAveragingTouchFilter.historyStart[id];
+            uint32_t end = mAveragingTouchFilter.historyEnd[id];
+
+            int64_t deltaX = x - mAveragingTouchFilter.historyData[end].pointers[id].x;
+            int64_t deltaY = y - mAveragingTouchFilter.historyData[end].pointers[id].y;
+            uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+#if DEBUG_HACKS
+            LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld",
+                    id, distance);
+#endif
+
+            if (distance < AVERAGING_DISTANCE_LIMIT) {
+                // Increment end index in preparation for recording new historical data.
+                end += 1;
+                if (end > AVERAGING_HISTORY_SIZE) {
+                    end = 0;
+                }
+
+                // If the end index has looped back to the start index then we have filled
+                // the historical trace up to the desired size so we drop the historical
+                // data at the start of the trace.
+                if (end == start) {
+                    start += 1;
+                    if (start > AVERAGING_HISTORY_SIZE) {
+                        start = 0;
+                    }
+                }
+
+                // Add the raw data to the historical trace.
+                mAveragingTouchFilter.historyStart[id] = start;
+                mAveragingTouchFilter.historyEnd[id] = end;
+                mAveragingTouchFilter.historyData[end].pointers[id].x = x;
+                mAveragingTouchFilter.historyData[end].pointers[id].y = y;
+                mAveragingTouchFilter.historyData[end].pointers[id].pressure = pressure;
+
+                // Average over all historical positions in the trace by total pressure.
+                int32_t averagedX = 0;
+                int32_t averagedY = 0;
+                int32_t totalPressure = 0;
+                for (;;) {
+                    int32_t historicalX = mAveragingTouchFilter.historyData[start].pointers[id].x;
+                    int32_t historicalY = mAveragingTouchFilter.historyData[start].pointers[id].y;
+                    int32_t historicalPressure = mAveragingTouchFilter.historyData[start]
+                            .pointers[id].pressure;
+
+                    averagedX += historicalX * historicalPressure;
+                    averagedY += historicalY * historicalPressure;
+                    totalPressure += historicalPressure;
+
+                    if (start == end) {
+                        break;
+                    }
+
+                    start += 1;
+                    if (start > AVERAGING_HISTORY_SIZE) {
+                        start = 0;
+                    }
+                }
+
+                averagedX /= totalPressure;
+                averagedY /= totalPressure;
+
+#if DEBUG_HACKS
+                LOGD("AveragingTouchFilter: Pointer id %d - "
+                        "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
+                        averagedX, averagedY);
+#endif
+
+                mCurrentTouch.pointers[currentIndex].x = averagedX;
+                mCurrentTouch.pointers[currentIndex].y = averagedY;
+            } else {
+#if DEBUG_HACKS
+                LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
+#endif
+            }
+        } else {
+#if DEBUG_HACKS
+            LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id);
+#endif
+        }
+
+        // Reset pointer history.
+        mAveragingTouchFilter.historyStart[id] = 0;
+        mAveragingTouchFilter.historyEnd[id] = 0;
+        mAveragingTouchFilter.historyData[0].pointers[id].x = x;
+        mAveragingTouchFilter.historyData[0].pointers[id].y = y;
+        mAveragingTouchFilter.historyData[0].pointers[id].pressure = pressure;
+    }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+    { // acquire virtual key lock
+        AutoMutex _l(mVirtualKeyLock);
+
+        if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) {
+            return AKEY_STATE_VIRTUAL;
+        }
+
+        for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mVirtualKeys[i];
+            if (virtualKey.keyCode == keyCode) {
+                return AKEY_STATE_UP;
+            }
+        }
+    } // release virtual key lock
+
+    return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    { // acquire virtual key lock
+        AutoMutex _l(mVirtualKeyLock);
+
+        if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) {
+            return AKEY_STATE_VIRTUAL;
+        }
+
+        for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mVirtualKeys[i];
+            if (virtualKey.scanCode == scanCode) {
+                return AKEY_STATE_UP;
+            }
+        }
+    } // release virtual key lock
+
+    return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+        const int32_t* keyCodes, uint8_t* outFlags) {
+    { // acquire virtual key lock
+        AutoMutex _l(mVirtualKeyLock);
+
+        for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+            const VirtualKey& virtualKey = mVirtualKeys[i];
+
+            for (size_t i = 0; i < numCodes; i++) {
+                if (virtualKey.keyCode == keyCodes[i]) {
+                    outFlags[i] = 1;
+                }
+            }
+        }
+    } // release virtual key lock
+
+    return true;
+}
+
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device, int32_t associatedDisplayId) :
+        TouchInputMapper(device, associatedDisplayId) {
+    initialize();
+}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {
+}
+
+void SingleTouchInputMapper::initialize() {
+    mAccumulator.clear();
+
+    mDown = false;
+    mX = 0;
+    mY = 0;
+    mPressure = 0;
+    mSize = 0;
+}
+
+void SingleTouchInputMapper::reset() {
+    TouchInputMapper::reset();
+
+    // Reinitialize.
+    initialize();
+ }
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_KEY:
+        switch (rawEvent->scanCode) {
+        case BTN_TOUCH:
+            mAccumulator.fields |= Accumulator::FIELD_BTN_TOUCH;
+            mAccumulator.btnTouch = rawEvent->value != 0;
+
+            sync(rawEvent->when);
+            mAccumulator.clear();
+            break;
+        }
+        break;
+
+    case EV_ABS:
+        switch (rawEvent->scanCode) {
+        case ABS_X:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_X;
+            mAccumulator.absX = rawEvent->value;
+            break;
+        case ABS_Y:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_Y;
+            mAccumulator.absY = rawEvent->value;
+            break;
+        case ABS_PRESSURE:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_PRESSURE;
+            mAccumulator.absPressure = rawEvent->value;
+            break;
+        case ABS_TOOL_WIDTH:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_TOOL_WIDTH;
+            mAccumulator.absToolWidth = rawEvent->value;
+            break;
+        }
+        break;
+
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_REPORT:
+            if (mAccumulator.isDirty()) {
+                sync(rawEvent->when);
+                mAccumulator.clear();
+            }
+            break;
+        }
+        break;
+    }
+}
+
+void SingleTouchInputMapper::sync(nsecs_t when) {
+    /* Update device state */
+
+    uint32_t fields = mAccumulator.fields;
+
+    if (fields & Accumulator::FIELD_BTN_TOUCH) {
+        mDown = mAccumulator.btnTouch;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_X) {
+        mX = mAccumulator.absX;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_Y) {
+        mY = mAccumulator.absY;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_PRESSURE) {
+        mPressure = mAccumulator.absPressure;
+    }
+
+    if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) {
+        mSize = mAccumulator.absToolWidth;
+    }
+
+    mCurrentTouch.clear();
+
+    if (mDown) {
+        mCurrentTouch.pointerCount = 1;
+        mCurrentTouch.pointers[0].id = 0;
+        mCurrentTouch.pointers[0].x = mX;
+        mCurrentTouch.pointers[0].y = mY;
+        mCurrentTouch.pointers[0].pressure = mPressure;
+        mCurrentTouch.pointers[0].size = mSize;
+        mCurrentTouch.pointers[0].touchMajor = mPressure;
+        mCurrentTouch.pointers[0].touchMinor = mPressure;
+        mCurrentTouch.pointers[0].toolMajor = mSize;
+        mCurrentTouch.pointers[0].toolMinor = mSize;
+        mCurrentTouch.pointers[0].orientation = 0;
+        mCurrentTouch.idToIndex[0] = 0;
+        mCurrentTouch.idBits.markBit(0);
+    }
+
+    syncTouch(when, true);
+}
+
+void SingleTouchInputMapper::configureAxes() {
+    TouchInputMapper::configureAxes();
+
+    // The axes are aliased to take into account the manner in which they are presented
+    // as part of the TouchData during the sync.
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mAxes.pressure);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mAxes.size);
+
+    mAxes.touchMajor = mAxes.pressure;
+    mAxes.touchMinor = mAxes.pressure;
+    mAxes.toolMajor = mAxes.size;
+    mAxes.toolMinor = mAxes.size;
+}
+
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device, int32_t associatedDisplayId) :
+        TouchInputMapper(device, associatedDisplayId) {
+    initialize();
+}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {
+}
+
+void MultiTouchInputMapper::initialize() {
+    mAccumulator.clear();
+}
+
+void MultiTouchInputMapper::reset() {
+    TouchInputMapper::reset();
+
+    // Reinitialize.
+    initialize();
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_ABS: {
+        uint32_t pointerIndex = mAccumulator.pointerCount;
+        Accumulator::Pointer* pointer = & mAccumulator.pointers[pointerIndex];
+
+        switch (rawEvent->scanCode) {
+        case ABS_MT_POSITION_X:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_X;
+            pointer->absMTPositionX = rawEvent->value;
+            break;
+        case ABS_MT_POSITION_Y:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_POSITION_Y;
+            pointer->absMTPositionY = rawEvent->value;
+            break;
+        case ABS_MT_TOUCH_MAJOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
+            pointer->absMTTouchMajor = rawEvent->value;
+            break;
+        case ABS_MT_TOUCH_MINOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_TOUCH_MINOR;
+            pointer->absMTTouchMinor = rawEvent->value;
+            break;
+        case ABS_MT_WIDTH_MAJOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
+            pointer->absMTWidthMajor = rawEvent->value;
+            break;
+        case ABS_MT_WIDTH_MINOR:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_WIDTH_MINOR;
+            pointer->absMTWidthMinor = rawEvent->value;
+            break;
+        case ABS_MT_ORIENTATION:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_ORIENTATION;
+            pointer->absMTOrientation = rawEvent->value;
+            break;
+        case ABS_MT_TRACKING_ID:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
+            pointer->absMTTrackingId = rawEvent->value;
+            break;
+        }
+        break;
+    }
+
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_MT_REPORT: {
+            // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+            uint32_t pointerIndex = mAccumulator.pointerCount;
+
+            if (mAccumulator.pointers[pointerIndex].fields) {
+                if (pointerIndex == MAX_POINTERS) {
+                    LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
+                            MAX_POINTERS);
+                } else {
+                    pointerIndex += 1;
+                    mAccumulator.pointerCount = pointerIndex;
+                }
+            }
+
+            mAccumulator.pointers[pointerIndex].clear();
+            break;
+        }
+
+        case SYN_REPORT:
+            if (mAccumulator.isDirty()) {
+                sync(rawEvent->when);
+                mAccumulator.clear();
+            }
+            break;
+        }
+        break;
+    }
+}
+
+void MultiTouchInputMapper::sync(nsecs_t when) {
+    static const uint32_t REQUIRED_FIELDS =
+            Accumulator::FIELD_ABS_MT_POSITION_X
+            | Accumulator::FIELD_ABS_MT_POSITION_Y
+            | Accumulator::FIELD_ABS_MT_TOUCH_MAJOR
+            | Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
 
     /* Update device state */
 
-    uint32_t fields = device->trackball.accumulator.fields;
-    bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
-    bool deltaChanged = fields & DELTA_FIELDS;
+    uint32_t inCount = mAccumulator.pointerCount;
+    uint32_t outCount = 0;
+    bool havePointerIds = true;
 
-    bool down;
-    if (downChanged) {
-        if (device->trackball.accumulator.btnMouse) {
-            device->trackball.current.down = true;
-            device->trackball.current.downTime = when;
-            down = true;
-        } else {
-            device->trackball.current.down = false;
-            down = false;
-        }
-    } else {
-        down = device->trackball.current.down;
-    }
+    mCurrentTouch.clear();
 
-    /* Apply policy */
+    for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
+        uint32_t fields = mAccumulator.pointers[inIndex].fields;
 
-    int32_t policyActions = mPolicy->interceptTrackball(when, downChanged, down, deltaChanged);
-
-    uint32_t policyFlags = 0;
-    if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
-        return; // event dropped
-    }
-
-    /* Enqueue motion event for dispatch */
-
-    int32_t motionEventAction;
-    if (downChanged) {
-        motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
-    } else {
-        motionEventAction = AMOTION_EVENT_ACTION_MOVE;
-    }
-
-    int32_t pointerId = 0;
-    PointerCoords pointerCoords;
-    pointerCoords.x = fields & InputDevice::TrackballState::Accumulator::FIELD_REL_X
-            ? device->trackball.accumulator.relX * device->trackball.precalculated.xScale : 0;
-    pointerCoords.y = fields & InputDevice::TrackballState::Accumulator::FIELD_REL_Y
-            ? device->trackball.accumulator.relY * device->trackball.precalculated.yScale : 0;
-    pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise.
-    pointerCoords.size = 0;
-    pointerCoords.touchMajor = 0;
-    pointerCoords.touchMinor = 0;
-    pointerCoords.toolMajor = 0;
-    pointerCoords.toolMinor = 0;
-    pointerCoords.orientation = 0;
-
-    float temp;
-    switch (mDisplayOrientation) {
-    case InputReaderPolicyInterface::ROTATION_90:
-        temp = pointerCoords.x;
-        pointerCoords.x = pointerCoords.y;
-        pointerCoords.y = - temp;
-        break;
-
-    case InputReaderPolicyInterface::ROTATION_180:
-        pointerCoords.x = - pointerCoords.x;
-        pointerCoords.y = - pointerCoords.y;
-        break;
-
-    case InputReaderPolicyInterface::ROTATION_270:
-        temp = pointerCoords.x;
-        pointerCoords.x = - pointerCoords.y;
-        pointerCoords.y = temp;
-        break;
-    }
-
-    mDispatcher->notifyMotion(when, device->id, AINPUT_SOURCE_TRACKBALL, policyFlags,
-            motionEventAction, globalMetaState(), AMOTION_EVENT_EDGE_FLAG_NONE,
-            1, & pointerId, & pointerCoords,
-            device->trackball.precalculated.xPrecision,
-            device->trackball.precalculated.yPrecision,
-            device->trackball.current.downTime);
-}
-
-void InputReader::onConfigurationChanged(nsecs_t when) {
-    // Reset global meta state because it depends on the list of all configured devices.
-    resetGlobalMetaState();
-
-    // Reset virtual keys, just in case.
-    updateExportedVirtualKeyState();
-
-    // Update input configuration.
-    updateExportedInputConfiguration();
-
-    // Enqueue configuration changed.
-    mDispatcher->notifyConfigurationChanged(when);
-}
-
-bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when,
-        int32_t policyActions, uint32_t* policyFlags) {
-    if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) {
-        mDispatcher->notifyAppSwitchComing(when);
-    }
-
-    if (policyActions & InputReaderPolicyInterface::ACTION_WOKE_HERE) {
-        *policyFlags |= POLICY_FLAG_WOKE_HERE;
-    }
-
-    if (policyActions & InputReaderPolicyInterface::ACTION_BRIGHT_HERE) {
-        *policyFlags |= POLICY_FLAG_BRIGHT_HERE;
-    }
-
-    return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH;
-}
-
-void InputReader::resetDisplayProperties() {
-    mDisplayWidth = mDisplayHeight = -1;
-    mDisplayOrientation = -1;
-}
-
-bool InputReader::refreshDisplayProperties() {
-    int32_t newWidth, newHeight, newOrientation;
-    if (mPolicy->getDisplayInfo(0, & newWidth, & newHeight, & newOrientation)) {
-        if (newWidth != mDisplayWidth || newHeight != mDisplayHeight) {
-            LOGD("Display size changed from %dx%d to %dx%d, updating device configuration",
-                    mDisplayWidth, mDisplayHeight, newWidth, newHeight);
-
-            mDisplayWidth = newWidth;
-            mDisplayHeight = newHeight;
-
-            for (size_t i = 0; i < mDevices.size(); i++) {
-                configureDeviceForCurrentDisplaySize(mDevices.valueAt(i));
-            }
+        if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
+#if DEBUG_POINTERS
+            LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d",
+                    inIndex, fields);
+            continue;
+#endif
         }
 
-        if (newOrientation != mDisplayOrientation) {
-            LOGD("Display orientation changed to %d", mDisplayOrientation);
-
-            mDisplayOrientation = newOrientation;
-        }
-        return true;
-    } else {
-        resetDisplayProperties();
-        return false;
-    }
-}
-
-InputDevice* InputReader::getDevice(int32_t deviceId) {
-    ssize_t index = mDevices.indexOfKey(deviceId);
-    return index >= 0 ? mDevices.valueAt((size_t) index) : NULL;
-}
-
-InputDevice* InputReader::getNonIgnoredDevice(int32_t deviceId) {
-    InputDevice* device = getDevice(deviceId);
-    return device && ! device->ignored ? device : NULL;
-}
-
-void InputReader::addDevice(nsecs_t when, int32_t deviceId) {
-    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
-    String8 name = mEventHub->getDeviceName(deviceId);
-    InputDevice* device = new InputDevice(deviceId, classes, name);
-
-    if (classes != 0) {
-        LOGI("Device added: id=0x%x, name=%s, classes=%02x", device->id,
-                device->name.string(), device->classes);
-
-        configureDevice(device);
-    } else {
-        LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", device->id,
-                device->name.string());
-
-        device->ignored = true;
-    }
-
-    device->reset();
-
-    mDevices.add(deviceId, device);
-
-    if (! device->ignored) {
-        onConfigurationChanged(when);
-    }
-}
-
-void InputReader::removeDevice(nsecs_t when, InputDevice* device) {
-    mDevices.removeItem(device->id);
-
-    if (! device->ignored) {
-        LOGI("Device removed: id=0x%x, name=%s, classes=%02x", device->id,
-                device->name.string(), device->classes);
-
-        onConfigurationChanged(when);
-    } else {
-        LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", device->id,
-                device->name.string());
-    }
-
-    delete device;
-}
-
-void InputReader::configureDevice(InputDevice* device) {
-    if (device->isMultiTouchScreen()) {
-        configureAbsoluteAxisInfo(device, ABS_MT_POSITION_X, "X",
-                & device->touchScreen.parameters.xAxis);
-        configureAbsoluteAxisInfo(device, ABS_MT_POSITION_Y, "Y",
-                & device->touchScreen.parameters.yAxis);
-        configureAbsoluteAxisInfo(device, ABS_MT_TOUCH_MAJOR, "Pressure",
-                & device->touchScreen.parameters.pressureAxis);
-        configureAbsoluteAxisInfo(device, ABS_MT_WIDTH_MAJOR, "Size",
-                & device->touchScreen.parameters.sizeAxis);
-        configureAbsoluteAxisInfo(device, ABS_MT_ORIENTATION, "Orientation",
-                & device->touchScreen.parameters.orientationAxis);
-    } else if (device->isSingleTouchScreen()) {
-        configureAbsoluteAxisInfo(device, ABS_X, "X",
-                & device->touchScreen.parameters.xAxis);
-        configureAbsoluteAxisInfo(device, ABS_Y, "Y",
-                & device->touchScreen.parameters.yAxis);
-        configureAbsoluteAxisInfo(device, ABS_PRESSURE, "Pressure",
-                & device->touchScreen.parameters.pressureAxis);
-        configureAbsoluteAxisInfo(device, ABS_TOOL_WIDTH, "Size",
-                & device->touchScreen.parameters.sizeAxis);
-        device->touchScreen.parameters.orientationAxis.valid = false;
-    }
-
-    if (device->isTouchScreen()) {
-        device->touchScreen.parameters.useBadTouchFilter =
-                mPolicy->filterTouchEvents();
-        device->touchScreen.parameters.useAveragingTouchFilter =
-                mPolicy->filterTouchEvents();
-        device->touchScreen.parameters.useJumpyTouchFilter =
-                mPolicy->filterJumpyTouchEvents();
-
-        if (device->touchScreen.parameters.pressureAxis.valid) {
-            device->touchScreen.precalculated.pressureOrigin =
-                    device->touchScreen.parameters.pressureAxis.minValue;
-            device->touchScreen.precalculated.pressureScale =
-                    1.0f / device->touchScreen.parameters.pressureAxis.range;
-        } else {
-            device->touchScreen.precalculated.pressureOrigin = 0;
-            device->touchScreen.precalculated.pressureScale = 1.0f;
-        }
-
-        if (device->touchScreen.parameters.sizeAxis.valid) {
-            device->touchScreen.precalculated.sizeOrigin =
-                    device->touchScreen.parameters.sizeAxis.minValue;
-            device->touchScreen.precalculated.sizeScale =
-                    1.0f / device->touchScreen.parameters.sizeAxis.range;
-        } else {
-            device->touchScreen.precalculated.sizeOrigin = 0;
-            device->touchScreen.precalculated.sizeScale = 1.0f;
-        }
-
-        if (device->touchScreen.parameters.orientationAxis.valid
-                && device->touchScreen.parameters.orientationAxis.maxValue > 0) {
-            device->touchScreen.precalculated.orientationScale =
-                    M_PI_4 / device->touchScreen.parameters.orientationAxis.maxValue;
-        } else {
-            device->touchScreen.precalculated.orientationScale = 0.0f;
-        }
-    }
-
-    if (device->isTrackball()) {
-        device->trackball.precalculated.xPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
-        device->trackball.precalculated.yPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
-        device->trackball.precalculated.xScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
-        device->trackball.precalculated.yScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
-    }
-
-    configureDeviceForCurrentDisplaySize(device);
-}
-
-void InputReader::configureDeviceForCurrentDisplaySize(InputDevice* device) {
-    if (device->isTouchScreen()) {
-        if (device->touchScreen.parameters.xAxis.valid
-                && device->touchScreen.parameters.yAxis.valid) {
-            device->touchScreen.precalculated.xOrigin =
-                    device->touchScreen.parameters.xAxis.minValue;
-            device->touchScreen.precalculated.yOrigin =
-                    device->touchScreen.parameters.yAxis.minValue;
-
-            if (mDisplayWidth < 0) {
-                LOGD("Skipping part of touch screen configuration since display size is unknown.");
-
-                device->touchScreen.precalculated.xScale = 1.0f;
-                device->touchScreen.precalculated.yScale = 1.0f;
-            } else {
-                LOGI("Device configured: id=0x%x, name=%s (display size was changed)", device->id,
-                        device->name.string());
-
-                device->touchScreen.precalculated.xScale =
-                        float(mDisplayWidth) / device->touchScreen.parameters.xAxis.range;
-                device->touchScreen.precalculated.yScale =
-                        float(mDisplayHeight) / device->touchScreen.parameters.yAxis.range;
-
-                configureVirtualKeys(device);
-            }
-        } else {
-            device->touchScreen.precalculated.xOrigin = 0;
-            device->touchScreen.precalculated.xScale = 1.0f;
-            device->touchScreen.precalculated.yOrigin = 0;
-            device->touchScreen.precalculated.yScale = 1.0f;
-        }
-    }
-}
-
-void InputReader::configureVirtualKeys(InputDevice* device) {
-    assert(device->touchScreen.parameters.xAxis.valid
-            && device->touchScreen.parameters.yAxis.valid);
-
-    device->touchScreen.virtualKeys.clear();
-
-    Vector<InputReaderPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions;
-    mPolicy->getVirtualKeyDefinitions(device->name, virtualKeyDefinitions);
-    if (virtualKeyDefinitions.size() == 0) {
-        return;
-    }
-
-    device->touchScreen.virtualKeys.setCapacity(virtualKeyDefinitions.size());
-
-    int32_t touchScreenLeft = device->touchScreen.parameters.xAxis.minValue;
-    int32_t touchScreenTop = device->touchScreen.parameters.yAxis.minValue;
-    int32_t touchScreenWidth = device->touchScreen.parameters.xAxis.range;
-    int32_t touchScreenHeight = device->touchScreen.parameters.yAxis.range;
-
-    for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
-        const InputReaderPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition =
-                virtualKeyDefinitions[i];
-
-        device->touchScreen.virtualKeys.add();
-        InputDevice::VirtualKey& virtualKey =
-                device->touchScreen.virtualKeys.editTop();
-
-        virtualKey.scanCode = virtualKeyDefinition.scanCode;
-        int32_t keyCode;
-        uint32_t flags;
-        if (mEventHub->scancodeToKeycode(device->id, virtualKey.scanCode,
-                & keyCode, & flags)) {
-            LOGW("  VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
-            device->touchScreen.virtualKeys.pop(); // drop the key
+        if (mAccumulator.pointers[inIndex].absMTTouchMajor <= 0) {
+            // Pointer is not down.  Drop it.
             continue;
         }
 
-        virtualKey.keyCode = keyCode;
-        virtualKey.flags = flags;
+        mCurrentTouch.pointers[outCount].x = mAccumulator.pointers[inIndex].absMTPositionX;
+        mCurrentTouch.pointers[outCount].y = mAccumulator.pointers[inIndex].absMTPositionY;
 
-        // convert the key definition's display coordinates into touch coordinates for a hit box
-        int32_t halfWidth = virtualKeyDefinition.width / 2;
-        int32_t halfHeight = virtualKeyDefinition.height / 2;
+        mCurrentTouch.pointers[outCount].touchMajor =
+                mAccumulator.pointers[inIndex].absMTTouchMajor;
+        mCurrentTouch.pointers[outCount].touchMinor =
+                (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) != 0
+                ? mAccumulator.pointers[inIndex].absMTTouchMinor
+                        : mAccumulator.pointers[inIndex].absMTTouchMajor;
 
-        virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
-                * touchScreenWidth / mDisplayWidth + touchScreenLeft;
-        virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
-                * touchScreenWidth / mDisplayWidth + touchScreenLeft;
-        virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
-                * touchScreenHeight / mDisplayHeight + touchScreenTop;
-        virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
-                * touchScreenHeight / mDisplayHeight + touchScreenTop;
+        mCurrentTouch.pointers[outCount].toolMajor =
+                mAccumulator.pointers[inIndex].absMTWidthMajor;
+        mCurrentTouch.pointers[outCount].toolMinor =
+                (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) != 0
+                ? mAccumulator.pointers[inIndex].absMTWidthMinor
+                        : mAccumulator.pointers[inIndex].absMTWidthMajor;
 
-        LOGI("  VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
-                virtualKey.scanCode, virtualKey.keyCode,
-                virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
-    }
-}
+        mCurrentTouch.pointers[outCount].orientation =
+                (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) != 0
+                ? mAccumulator.pointers[inIndex].absMTOrientation : 0;
 
-void InputReader::configureAbsoluteAxisInfo(InputDevice* device,
-        int axis, const char* name, InputDevice::AbsoluteAxisInfo* out) {
-    if (! mEventHub->getAbsoluteInfo(device->id, axis,
-            & out->minValue, & out->maxValue, & out->flat, &out->fuzz)) {
-        out->range = out->maxValue - out->minValue;
-        if (out->range != 0) {
-            LOGI("  %s: min=%d max=%d flat=%d fuzz=%d",
-                    name, out->minValue, out->maxValue, out->flat, out->fuzz);
-            out->valid = true;
-            return;
-        }
-    }
+        // Derive an approximation of pressure and size.
+        // FIXME assignment of pressure may be incorrect, probably better to let
+        // pressure = touch / width.  Later on we pass width to MotionEvent as a size, which
+        // isn't quite right either.  Should be using touch for that.
+        mCurrentTouch.pointers[outCount].pressure = mAccumulator.pointers[inIndex].absMTTouchMajor;
+        mCurrentTouch.pointers[outCount].size = mAccumulator.pointers[inIndex].absMTWidthMajor;
 
-    out->valid = false;
-    out->minValue = 0;
-    out->maxValue = 0;
-    out->flat = 0;
-    out->fuzz = 0;
-    out->range = 0;
-    LOGI("  %s: unknown axis values, marking as invalid", name);
-}
+        if (havePointerIds) {
+            if (fields & Accumulator::
+                    FIELD_ABS_MT_TRACKING_ID) {
+                uint32_t id = uint32_t(mAccumulator.pointers[inIndex].absMTTrackingId);
 
-void InputReader::configureExcludedDevices() {
-    Vector<String8> excludedDeviceNames;
-    mPolicy->getExcludedDeviceNames(excludedDeviceNames);
-
-    for (size_t i = 0; i < excludedDeviceNames.size(); i++) {
-        mEventHub->addExcludedDevice(excludedDeviceNames[i]);
-    }
-}
-
-void InputReader::resetGlobalMetaState() {
-    mGlobalMetaState = -1;
-}
-
-int32_t InputReader::globalMetaState() {
-    if (mGlobalMetaState == -1) {
-        mGlobalMetaState = 0;
-        for (size_t i = 0; i < mDevices.size(); i++) {
-            InputDevice* device = mDevices.valueAt(i);
-            if (device->isKeyboard()) {
-                mGlobalMetaState |= device->keyboard.current.metaState;
+                if (id > MAX_POINTER_ID) {
+#if DEBUG_POINTERS
+                    LOGD("Pointers: Ignoring driver provided pointer id %d because "
+                            "it is larger than max supported id %d for optimizations",
+                            id, MAX_POINTER_ID);
+#endif
+                    havePointerIds = false;
+                }
+                else {
+                    mCurrentTouch.pointers[outCount].id = id;
+                    mCurrentTouch.idToIndex[id] = outCount;
+                    mCurrentTouch.idBits.markBit(id);
+                }
+            } else {
+                havePointerIds = false;
             }
         }
-    }
-    return mGlobalMetaState;
-}
 
-void InputReader::updateExportedVirtualKeyState() {
-    int32_t keyCode = -1, scanCode = -1;
-
-    for (size_t i = 0; i < mDevices.size(); i++) {
-        InputDevice* device = mDevices.valueAt(i);
-        if (device->isTouchScreen()) {
-            if (device->touchScreen.currentVirtualKey.status
-                    == InputDevice::TouchScreenState::CurrentVirtualKeyState::STATUS_DOWN) {
-                keyCode = device->touchScreen.currentVirtualKey.keyCode;
-                scanCode = device->touchScreen.currentVirtualKey.scanCode;
-            }
-        }
+        outCount += 1;
     }
 
-    { // acquire exported state lock
-        AutoMutex _l(mExportedStateLock);
+    mCurrentTouch.pointerCount = outCount;
 
-        mExportedVirtualKeyCode = keyCode;
-        mExportedVirtualScanCode = scanCode;
-    } // release exported state lock
+    syncTouch(when, havePointerIds);
 }
 
-bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const {
-    { // acquire exported state lock
-        AutoMutex _l(mExportedStateLock);
+void MultiTouchInputMapper::configureAxes() {
+    TouchInputMapper::configureAxes();
 
-        *outKeyCode = mExportedVirtualKeyCode;
-        *outScanCode = mExportedVirtualScanCode;
-        return mExportedVirtualKeyCode != -1;
-    } // release exported state lock
-}
+    // The axes are aliased to take into account the manner in which they are presented
+    // as part of the TouchData during the sync.
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mAxes.touchMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mAxes.touchMinor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mAxes.toolMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mAxes.toolMinor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mAxes.orientation);
 
-void InputReader::updateExportedInputConfiguration() {
-    int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH;
-    int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS;
-    int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV;
-
-    for (size_t i = 0; i < mDevices.size(); i++) {
-        InputDevice* device = mDevices.valueAt(i);
-        int32_t deviceClasses = device->classes;
-
-        if (deviceClasses & INPUT_DEVICE_CLASS_TOUCHSCREEN) {
-            touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER;
-        }
-        if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) {
-            keyboardConfig = InputConfiguration::KEYBOARD_QWERTY;
-        }
-        if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) {
-            navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL;
-        } else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) {
-            navigationConfig = InputConfiguration::NAVIGATION_DPAD;
-        }
+    if (! mAxes.touchMinor.valid) {
+        mAxes.touchMinor = mAxes.touchMajor;
     }
 
-    { // acquire exported state lock
-        AutoMutex _l(mExportedStateLock);
+    if (! mAxes.toolMinor.valid) {
+        mAxes.toolMinor = mAxes.toolMajor;
+    }
 
-        mExportedInputConfiguration.touchScreen = touchScreenConfig;
-        mExportedInputConfiguration.keyboard = keyboardConfig;
-        mExportedInputConfiguration.navigation = navigationConfig;
-    } // release exported state lock
+    mAxes.pressure = mAxes.touchMajor;
+    mAxes.size = mAxes.toolMajor;
 }
 
-void InputReader::getCurrentInputConfiguration(InputConfiguration* outConfiguration) const {
-    { // acquire exported state lock
-        AutoMutex _l(mExportedStateLock);
-
-        *outConfiguration = mExportedInputConfiguration;
-    } // release exported state lock
-}
-
-int32_t InputReader::getCurrentScanCodeState(int32_t deviceId, int32_t deviceClasses,
-        int32_t scanCode) const {
-    { // acquire exported state lock
-        AutoMutex _l(mExportedStateLock);
-
-        if (mExportedVirtualScanCode == scanCode) {
-            return AKEY_STATE_VIRTUAL;
-        }
-    } // release exported state lock
-
-    return mEventHub->getScanCodeState(deviceId, deviceClasses, scanCode);
-}
-
-int32_t InputReader::getCurrentKeyCodeState(int32_t deviceId, int32_t deviceClasses,
-        int32_t keyCode) const {
-    { // acquire exported state lock
-        AutoMutex _l(mExportedStateLock);
-
-        if (mExportedVirtualKeyCode == keyCode) {
-            return AKEY_STATE_VIRTUAL;
-        }
-    } // release exported state lock
-
-    return mEventHub->getKeyCodeState(deviceId, deviceClasses, keyCode);
-}
-
-int32_t InputReader::getCurrentSwitchState(int32_t deviceId, int32_t deviceClasses,
-        int32_t sw) const {
-    return mEventHub->getSwitchState(deviceId, deviceClasses, sw);
-}
-
-bool InputReader::hasKeys(size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) const {
-    return mEventHub->hasKeys(numCodes, keyCodes, outFlags);
-}
-
-
-// --- InputReaderThread ---
-
-InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
-        Thread(/*canCallJava*/ true), mReader(reader) {
-}
-
-InputReaderThread::~InputReaderThread() {
-}
-
-bool InputReaderThread::threadLoop() {
-    mReader->loopOnce();
-    return true;
-}
 
 } // namespace android
diff --git a/location/java/android/location/Country.aidl b/location/java/android/location/Country.aidl
new file mode 100644
index 0000000..c83d645
--- /dev/null
+++ b/location/java/android/location/Country.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+parcelable Country;
\ No newline at end of file
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
new file mode 100755
index 0000000..3c05403
--- /dev/null
+++ b/location/java/android/location/Country.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class wraps the country information.
+ *
+ * @hide
+ */
+public class Country implements Parcelable {
+    /**
+     * The country code came from the mobile network
+     */
+    public static final int COUNTRY_SOURCE_NETWORK = 0;
+
+    /**
+     * The country code came from the location service
+     */
+    public static final int COUNTRY_SOURCE_LOCATION = 1;
+
+    /**
+     * The country code was read from the SIM card
+     */
+    public static final int COUNTRY_SOURCE_SIM = 2;
+
+    /**
+     * The country code came from the system locale setting
+     */
+    public static final int COUNTRY_SOURCE_LOCALE = 3;
+
+    /**
+     * The ISO 3166-1 two letters country code.
+     */
+    private final String mCountryIso;
+
+    /**
+     * Where the country code came from.
+     */
+    private final int mSource;
+
+    private int mHashCode;
+    /**
+     *
+     * @param countryIso the ISO 3166-1 two letters country code.
+     * @param source where the countryIso came from, could be one of below
+     *        values
+     *        <p>
+     *        <ul>
+     *        <li>{@link #COUNTRY_SOURCE_NETWORK}</li>
+     *        <li>{@link #COUNTRY_SOURCE_LOCATION}</li>
+     *        <li>{@link #COUNTRY_SOURCE_SIM}</li>
+     *        <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
+     *        </ul>
+     */
+    public Country(final String countryIso, final int source) {
+        if (countryIso == null || source < COUNTRY_SOURCE_NETWORK
+                || source > COUNTRY_SOURCE_LOCALE) {
+            throw new IllegalArgumentException();
+        }
+        mCountryIso = countryIso.toLowerCase();
+        mSource = source;
+    }
+
+    public Country(Country country) {
+        mCountryIso = country.mCountryIso;
+        mSource = country.mSource;
+    }
+
+    /**
+     * @return the ISO 3166-1 two letters country code
+     */
+    public final String getCountryIso() {
+        return mCountryIso;
+    }
+
+    /**
+     * @return where the country code came from, could be one of below values
+     *         <p>
+     *         <ul>
+     *         <li>{@link #COUNTRY_SOURCE_NETWORK}</li>
+     *         <li>{@link #COUNTRY_SOURCE_LOCATION}</li>
+     *         <li>{@link #COUNTRY_SOURCE_SIM}</li>
+     *         <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
+     *         </ul>
+     */
+    public final int getSource() {
+        return mSource;
+    }
+
+    public static final Parcelable.Creator<Country> CREATOR = new Parcelable.Creator<Country>() {
+        public Country createFromParcel(Parcel in) {
+            return new Country(in.readString(), in.readInt());
+        }
+
+        public Country[] newArray(int size) {
+            return new Country[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mCountryIso);
+        parcel.writeInt(mSource);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object == this) {
+            return true;
+        }
+        if (object instanceof Country) {
+            Country c = (Country) object;
+            return mCountryIso.equals(c.getCountryIso()) && mSource == c.getSource();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mHashCode;
+        if (hash == 0) {
+            hash = 17;
+            hash = hash * 13 + mCountryIso.hashCode();
+            hash = hash * 13 + mSource;
+            mHashCode = hash;
+        }
+        return mHashCode;
+    }
+
+    /**
+     * Compare the specified country to this country object ignoring the mSource
+     * field, return true if the countryIso fields are equal
+     *
+     * @param country the country to compare
+     * @return true if the specified country's countryIso field is equal to this
+     *         country's, false otherwise.
+     */
+    public boolean equalsIgnoreSource(Country country) {
+        return country != null && mCountryIso.equals(country.getCountryIso());
+    }
+}
diff --git a/location/java/android/location/CountryDetector.java b/location/java/android/location/CountryDetector.java
new file mode 100644
index 0000000..0b780ce
--- /dev/null
+++ b/location/java/android/location/CountryDetector.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.location;
+
+import java.util.HashMap;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides access to the system country detector service. This
+ * service allows applications to obtain the country that the user is in.
+ * <p>
+ * The country will be detected in order of reliability, like
+ * <ul>
+ * <li>Mobile network</li>
+ * <li>Location</li>
+ * <li>SIM's country</li>
+ * <li>Phone's locale</li>
+ * </ul>
+ * <p>
+ * Call the {@link #detectCountry()} to get the available country immediately.
+ * <p>
+ * To be notified of the future country change, use the
+ * {@link #addCountryListener}
+ * <p>
+ * <p>
+ * You do not instantiate this class directly; instead, retrieve it through
+ * {@link android.content.Context#getSystemService
+ * Context.getSystemService(Context.COUNTRY_DETECTOR)}.
+ * <p>
+ * Both ACCESS_FINE_LOCATION and ACCESS_COARSE_LOCATION permissions are needed.
+ *
+ * @hide
+ */
+public class CountryDetector {
+
+    /**
+     * The class to wrap the ICountryListener.Stub and CountryListener objects
+     * together. The CountryListener will be notified through the specific
+     * looper once the country changed and detected.
+     */
+    private final static class ListenerTransport extends ICountryListener.Stub {
+
+        private final CountryListener mListener;
+
+        private final Handler mHandler;
+
+        public ListenerTransport(CountryListener listener, Looper looper) {
+            mListener = listener;
+            if (looper != null) {
+                mHandler = new Handler(looper);
+            } else {
+                mHandler = new Handler();
+            }
+        }
+
+        public void onCountryDetected(final Country country) {
+            mHandler.post(new Runnable() {
+                public void run() {
+                    mListener.onCountryDetected(country);
+                }
+            });
+        }
+    }
+
+    private final static String TAG = "CountryDetector";
+    private final ICountryDetector mService;
+    private final HashMap<CountryListener, ListenerTransport> mListeners;
+
+    /**
+     * @hide - hide this constructor because it has a parameter of type
+     *       ICountryDetector, which is a system private class. The right way to
+     *       create an instance of this class is using the factory
+     *       Context.getSystemService.
+     */
+    public CountryDetector(ICountryDetector service) {
+        mService = service;
+        mListeners = new HashMap<CountryListener, ListenerTransport>();
+    }
+
+    /**
+     * Start detecting the country that the user is in.
+     *
+     * @return the country if it is available immediately, otherwise null will
+     *         be returned.
+     */
+    public Country detectCountry() {
+        try {
+            return mService.detectCountry();
+        } catch (RemoteException e) {
+            Log.e(TAG, "detectCountry: RemoteException", e);
+            return null;
+        }
+    }
+
+    /**
+     * Add a listener to receive the notification when the country is detected
+     * or changed.
+     *
+     * @param listener will be called when the country is detected or changed.
+     * @param looper a Looper object whose message queue will be used to
+     *        implement the callback mechanism. If looper is null then the
+     *        callbacks will be called on the main thread.
+     */
+    public void addCountryListener(CountryListener listener, Looper looper) {
+        synchronized (mListeners) {
+            if (!mListeners.containsKey(listener)) {
+                ListenerTransport transport = new ListenerTransport(listener, looper);
+                try {
+                    mService.addCountryListener(transport);
+                    mListeners.put(listener, transport);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "addCountryListener: RemoteException", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove the listener
+     */
+    public void removeCountryListener(CountryListener listener) {
+        synchronized (mListeners) {
+            ListenerTransport transport = mListeners.get(listener);
+            if (transport != null) {
+                try {
+                    mListeners.remove(listener);
+                    mService.removeCountryListener(transport);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "removeCountryListener: RemoteException", e);
+                }
+            }
+        }
+    }
+}
diff --git a/location/java/android/location/CountryListener.java b/location/java/android/location/CountryListener.java
new file mode 100644
index 0000000..e36db41
--- /dev/null
+++ b/location/java/android/location/CountryListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+/**
+ * The listener for receiving the notification when the country is detected or
+ * changed
+ *
+ * @hide
+ */
+public interface CountryListener {
+    /**
+     * @param country the changed or detected country.
+     */
+    void onCountryDetected(Country country);
+}
diff --git a/location/java/android/location/ICountryDetector.aidl b/location/java/android/location/ICountryDetector.aidl
new file mode 100644
index 0000000..6eaf07c
--- /dev/null
+++ b/location/java/android/location/ICountryDetector.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.location.Country;
+import android.location.ICountryListener;
+
+/**
+ * The API for detecting the country where the user is.
+ *
+ * {@hide}
+ */
+interface ICountryDetector
+{
+    /**
+     * Start detecting the country that the user is in.
+     * @return the country if it is available immediately, otherwise null will be returned.
+     */
+    Country detectCountry();
+
+    /**
+     * Add a listener to receive the notification when the country is detected or changed.
+     */
+    void addCountryListener(in ICountryListener listener);
+
+    /**
+     * Remove the listener
+     */
+    void removeCountryListener(in ICountryListener listener);
+}
\ No newline at end of file
diff --git a/location/java/android/location/ICountryListener.aidl b/location/java/android/location/ICountryListener.aidl
new file mode 100644
index 0000000..76ecb13
--- /dev/null
+++ b/location/java/android/location/ICountryListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2010, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.location.Country;
+/**
+ * {@hide}
+ */
+oneway interface ICountryListener
+{
+    void onCountryDetected(in Country country);
+}
diff --git a/location/tests/locationtests/src/android/location/CountryTester.java b/location/tests/locationtests/src/android/location/CountryTester.java
new file mode 100644
index 0000000..9802d5a
--- /dev/null
+++ b/location/tests/locationtests/src/android/location/CountryTester.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.test.AndroidTestCase;
+
+public class CountryTester extends AndroidTestCase {
+    public void testCountryEquals() {
+        Country countryA = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+        Country countryB = new Country("US", Country.COUNTRY_SOURCE_LOCALE);
+        Country countryC = new Country("CN", Country.COUNTRY_SOURCE_LOCALE);
+        Country countryD = new Country("us", Country.COUNTRY_SOURCE_NETWORK);
+        assertTrue(countryA.equalsIgnoreSource(countryB));
+        assertFalse(countryA.equalsIgnoreSource(countryC));
+        assertFalse(countryA.equals(countryC));
+        assertTrue(countryA.equals(countryD));
+        assertTrue(countryA.hashCode() == countryD.hashCode());
+    }
+}
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index e3b9e36..8d9f4fe 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -698,8 +698,11 @@
         }
     }
 
-    lStatus = translateError(lpAudioEffect->command(cmdCode, cmdSize, pCmdData,
-            pReplySize, pReplyData));
+    lStatus = translateError(lpAudioEffect->command((uint32_t)cmdCode,
+                                                    (uint32_t)cmdSize,
+                                                    pCmdData,
+                                                    (uint32_t *)pReplySize,
+                                                    pReplyData));
 
 command_Exit:
 
diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c
index 0be280c..c19a505 100644
--- a/media/libeffects/factory/EffectsFactory.c
+++ b/media/libeffects/factory/EffectsFactory.c
@@ -73,7 +73,12 @@
     return ret;
 }
 
-int Effect_Command(effect_interface_t self, int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData)
+int Effect_Command(effect_interface_t self,
+                   uint32_t cmdCode,
+                   uint32_t cmdSize,
+                   void *pCmdData,
+                   uint32_t *replySize,
+                   void *pReplyData)
 {
     int ret = init();
     if (ret < 0) {
diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index 4440447..4c3ebca 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -2348,10 +2348,10 @@
 
 /* Effect Control Interface Implementation: Command */
 extern "C" int Effect_command(effect_interface_t  self,
-                              int                 cmdCode,
-                              int                 cmdSize,
+                              uint32_t            cmdCode,
+                              uint32_t            cmdSize,
                               void                *pCmdData,
-                              int                 *replySize,
+                              uint32_t            *replySize,
                               void                *pReplyData){
     EffectContext * pContext = (EffectContext *) self;
     int retsize;
diff --git a/media/libeffects/testlibs/EffectEqualizer.cpp b/media/libeffects/testlibs/EffectEqualizer.cpp
index 0eb2bdf..a71f236 100644
--- a/media/libeffects/testlibs/EffectEqualizer.cpp
+++ b/media/libeffects/testlibs/EffectEqualizer.cpp
@@ -551,8 +551,8 @@
     return 0;
 }   // end Equalizer_process
 
-extern "C" int Equalizer_command(effect_interface_t self, int cmdCode, int cmdSize,
-        void *pCmdData, int *replySize, void *pReplyData) {
+extern "C" int Equalizer_command(effect_interface_t self, uint32_t cmdCode, uint32_t cmdSize,
+        void *pCmdData, uint32_t *replySize, void *pReplyData) {
 
     android::EqualizerContext * pContext = (android::EqualizerContext *) self;
     int retsize;
diff --git a/media/libeffects/testlibs/EffectReverb.c b/media/libeffects/testlibs/EffectReverb.c
index 3f9069f..3eb8b2c 100644
--- a/media/libeffects/testlibs/EffectReverb.c
+++ b/media/libeffects/testlibs/EffectReverb.c
@@ -270,8 +270,8 @@
 }
 
 
-static int Reverb_Command(effect_interface_t self, int cmdCode, int cmdSize,
-        void *pCmdData, int *replySize, void *pReplyData) {
+static int Reverb_Command(effect_interface_t self, uint32_t cmdCode, uint32_t cmdSize,
+        void *pCmdData, uint32_t *replySize, void *pReplyData) {
     reverb_module_t *pRvbModule = (reverb_module_t *) self;
     reverb_object_t *pReverb;
     int retsize;
diff --git a/media/libeffects/testlibs/EffectReverb.h b/media/libeffects/testlibs/EffectReverb.h
index ee8e390..dbcd192 100644
--- a/media/libeffects/testlibs/EffectReverb.h
+++ b/media/libeffects/testlibs/EffectReverb.h
@@ -301,12 +301,23 @@
  *------------------------------------
 */
 int EffectQueryNumberEffects(uint32_t *pNumEffects);
-int EffectQueryEffect(uint32_t index, effect_descriptor_t *pDescriptor);
-int EffectCreate(effect_uuid_t *effectUID, int32_t sessionId, int32_t ioId, effect_interface_t *pInterface);
+int EffectQueryEffect(uint32_t index,
+                      effect_descriptor_t *pDescriptor);
+int EffectCreate(effect_uuid_t *effectUID,
+                 int32_t sessionId,
+                 int32_t ioId,
+                 effect_interface_t *pInterface);
 int EffectRelease(effect_interface_t interface);
 
-static int Reverb_Process(effect_interface_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer);
-static int Reverb_Command(effect_interface_t self, int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData);
+static int Reverb_Process(effect_interface_t self,
+                          audio_buffer_t *inBuffer,
+                          audio_buffer_t *outBuffer);
+static int Reverb_Command(effect_interface_t self,
+                          uint32_t cmdCode,
+                          uint32_t cmdSize,
+                          void *pCmdData,
+                          uint32_t *replySize,
+                          void *pReplyData);
 
 
 /*------------------------------------
diff --git a/media/libeffects/visualizer/EffectVisualizer.cpp b/media/libeffects/visualizer/EffectVisualizer.cpp
index ec13557..8ab57c93 100644
--- a/media/libeffects/visualizer/EffectVisualizer.cpp
+++ b/media/libeffects/visualizer/EffectVisualizer.cpp
@@ -272,8 +272,8 @@
     return 0;
 }   // end Visualizer_process
 
-extern "C" int Visualizer_command(effect_interface_t self, int cmdCode, int cmdSize,
-        void *pCmdData, int *replySize, void *pReplyData) {
+extern "C" int Visualizer_command(effect_interface_t self, uint32_t cmdCode, uint32_t cmdSize,
+        void *pCmdData, uint32_t *replySize, void *pReplyData) {
 
     android::VisualizerContext * pContext = (android::VisualizerContext *)self;
     int retsize;
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index df0f73b..3cdf48a 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -221,7 +221,11 @@
     return INVALID_OPERATION;
 }
 
-status_t AudioEffect::command(int32_t cmdCode, int32_t cmdSize, void *cmdData, int32_t *replySize, void *replyData)
+status_t AudioEffect::command(uint32_t cmdCode,
+                              uint32_t cmdSize,
+                              void *cmdData,
+                              uint32_t *replySize,
+                              void *replyData)
 {
     if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
         return INVALID_OPERATION;
@@ -241,8 +245,8 @@
         return BAD_VALUE;
     }
 
-    int size = sizeof(int);
-    int psize = ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize;
+    uint32_t size = sizeof(int);
+    uint32_t psize = ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize;
 
     LOGV("setParameter: param: %d, param2: %d", *(int *)param->data, (param->psize == 8) ? *((int *)param->data + 1): -1);
 
@@ -285,7 +289,7 @@
     if (mCblk->clientIndex == 0) {
         return INVALID_OPERATION;
     }
-    int size = 0;
+    uint32_t size = 0;
     return mIEffect->command(EFFECT_CMD_SET_PARAM_COMMIT, 0, NULL, &size, NULL);
 }
 
@@ -301,7 +305,7 @@
 
     LOGV("getParameter: param: %d, param2: %d", *(int *)param->data, (param->psize == 8) ? *((int *)param->data + 1): -1);
 
-    int psize = sizeof(effect_param_t) + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize;
+    uint32_t psize = sizeof(effect_param_t) + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize;
 
     return mIEffect->command(EFFECT_CMD_GET_PARAM, sizeof(effect_param_t) + param->psize, param, &psize, param);
 }
@@ -350,7 +354,11 @@
     }
 }
 
-void AudioEffect::commandExecuted(int cmdCode, int cmdSize, void *cmdData, int replySize, void *replyData)
+void AudioEffect::commandExecuted(uint32_t cmdCode,
+                                  uint32_t cmdSize,
+                                  void *cmdData,
+                                  uint32_t replySize,
+                                  void *replyData)
 {
     if (cmdData == NULL || replyData == NULL) {
         return;
diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp
index 8e3ac71..a945b97 100644
--- a/media/libmedia/IEffect.cpp
+++ b/media/libmedia/IEffect.cpp
@@ -59,7 +59,11 @@
         return reply.readInt32();
     }
 
-    status_t command(int cmdCode, int cmdSize, void *pCmdData, int *pReplySize, void *pReplyData)
+    status_t command(uint32_t cmdCode,
+                     uint32_t cmdSize,
+                     void *pCmdData,
+                     uint32_t *pReplySize,
+                     void *pReplyData)
     {
         LOGV("command");
         Parcel data, reply;
@@ -136,15 +140,15 @@
         case COMMAND: {
             LOGV("COMMAND");
             CHECK_INTERFACE(IEffect, data, reply);
-            int cmdCode = data.readInt32();
-            int cmdSize = data.readInt32();
+            uint32_t cmdCode = data.readInt32();
+            uint32_t cmdSize = data.readInt32();
             char *cmd = NULL;
             if (cmdSize) {
                 cmd = (char *)malloc(cmdSize);
                 data.read(cmd, cmdSize);
             }
-            int replySize = data.readInt32();
-            int replySz = replySize;
+            uint32_t replySize = data.readInt32();
+            uint32_t replySz = replySize;
             char *resp = NULL;
             if (replySize) {
                 resp = (char *)malloc(replySize);
diff --git a/media/libmedia/IEffectClient.cpp b/media/libmedia/IEffectClient.cpp
index e7659ae..1fa9cbe 100644
--- a/media/libmedia/IEffectClient.cpp
+++ b/media/libmedia/IEffectClient.cpp
@@ -56,7 +56,11 @@
         remote()->transact(ENABLE_STATUS_CHANGED, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
-    void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData)
+    void commandExecuted(uint32_t cmdCode,
+                         uint32_t cmdSize,
+                         void *pCmdData,
+                         uint32_t replySize,
+                         void *pReplyData)
     {
         LOGV("commandExecuted");
         Parcel data, reply;
@@ -108,14 +112,14 @@
         case COMMAND_EXECUTED: {
             LOGV("COMMAND_EXECUTED");
             CHECK_INTERFACE(IEffectClient, data, reply);
-            int cmdCode = data.readInt32();
-            int cmdSize = data.readInt32();
+            uint32_t cmdCode = data.readInt32();
+            uint32_t cmdSize = data.readInt32();
             char *cmd = NULL;
             if (cmdSize) {
                 cmd = (char *)malloc(cmdSize);
                 data.read(cmd, cmdSize);
             }
-            int replySize = data.readInt32();
+            uint32_t replySize = data.readInt32();
             char *resp = NULL;
             if (replySize) {
                 resp = (char *)malloc(replySize);
diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp
index 47e96e5..32cdb49 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -184,7 +184,7 @@
 
     status_t status = NO_ERROR;
     if (mEnabled) {
-        int32_t replySize = mCaptureSize;
+        uint32_t replySize = mCaptureSize;
         status_t status = command(VISU_CMD_CAPTURE, 0, NULL, &replySize, waveform);
         if (replySize == 0) {
             status = NOT_ENOUGH_DATA;
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index f747f90..b982f93 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1036,8 +1036,10 @@
         totalBitRate += mVideoBitRate;
     }
 
-    reinterpret_cast<MPEG4Writer *>(writer.get())->
-        setInterleaveDuration(mInterleaveDurationUs);
+    if (mInterleaveDurationUs > 0) {
+        reinterpret_cast<MPEG4Writer *>(writer.get())->
+            setInterleaveDuration(mInterleaveDurationUs);
+    }
 
     if (mMaxFileDurationUs != 0) {
         writer->setMaxFileDuration(mMaxFileDurationUs);
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index c4a25bc..16b1094 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -66,11 +66,7 @@
 
     pthread_t mThread;
 
-    struct SampleInfo {
-        size_t size;
-        int64_t timestampUs;
-    };
-    List<SampleInfo>    mSampleInfos;
+    List<size_t>        mSampleSizes;
     bool                mSamplesHaveSameSize;
 
     List<MediaBuffer *> mChunkSamples;
@@ -145,7 +141,7 @@
       mOffset(0),
       mMdatOffset(0),
       mEstimatedMoovBoxSize(0),
-      mInterleaveDurationUs(500000) {
+      mInterleaveDurationUs(1000000) {
     CHECK(mFile != NULL);
 }
 
@@ -157,7 +153,7 @@
       mOffset(0),
       mMdatOffset(0),
       mEstimatedMoovBoxSize(0),
-      mInterleaveDurationUs(500000) {
+      mInterleaveDurationUs(1000000) {
     CHECK(mFile != NULL);
 }
 
@@ -916,6 +912,15 @@
     return OK;
 }
 
+static bool collectStatisticalData() {
+    char value[PROPERTY_VALUE_MAX];
+    if (property_get("media.stagefright.record-stats", value, NULL)
+        && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
+        return true;
+    }
+    return false;
+}
+
 void MPEG4Writer::Track::threadEntry() {
     sp<MetaData> meta = mSource->getFormat();
     const char *mime;
@@ -935,6 +940,7 @@
     uint32_t previousSampleSize = 0;  // Size of the previous sample
     int64_t previousPausedDurationUs = 0;
     sp<MetaData> meta_data;
+    bool collectStats = collectStatisticalData();
 
     status_t err = OK;
     MediaBuffer *buffer;
@@ -1081,8 +1087,8 @@
 
         if (is_avc) StripStartcode(copy);
 
-        SampleInfo info;
-        info.size = is_avc
+        size_t sampleSize;
+        sampleSize = is_avc
 #if USE_NALLEN_FOUR
                 ? copy->range_length() + 4
 #else
@@ -1091,7 +1097,7 @@
                 : copy->range_length();
 
         // Max file size or duration handling
-        mEstimatedTrackSizeBytes += info.size;
+        mEstimatedTrackSizeBytes += sampleSize;
         if (mOwner->exceedsFileSizeLimit()) {
             mOwner->notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0);
             break;
@@ -1109,7 +1115,7 @@
         CHECK(meta_data->findInt64(kKeyTime, &timestampUs));
 
 ////////////////////////////////////////////////////////////////////////////////
-        if (mSampleInfos.empty()) {
+        if (mSampleSizes.empty()) {
             mStartTimestampUs = timestampUs;
             mOwner->setStartTimestampUs(mStartTimestampUs);
         }
@@ -1126,10 +1132,9 @@
             mMaxTimeStampUs = timestampUs;
         }
 
-        info.timestampUs = timestampUs;
-        mSampleInfos.push_back(info);
-        if (mSampleInfos.size() > 2) {
-            if (lastDurationUs != info.timestampUs - lastTimestampUs) {
+        mSampleSizes.push_back(sampleSize);
+        if (mSampleSizes.size() > 2) {
+            if (lastDurationUs != timestampUs - lastTimestampUs) {
                 SttsTableEntry sttsEntry(sampleCount, lastDurationUs);
                 mSttsTableEntries.push_back(sttsEntry);
                 sampleCount = 1;
@@ -1138,16 +1143,16 @@
             }
         }
         if (mSamplesHaveSameSize) {
-            if (mSampleInfos.size() >= 2 && previousSampleSize != info.size) {
+            if (mSampleSizes.size() >= 2 && previousSampleSize != sampleSize) {
                 mSamplesHaveSameSize = false;
             }
-            previousSampleSize = info.size;
+            previousSampleSize = sampleSize;
         }
-        lastDurationUs = info.timestampUs - lastTimestampUs;
-        lastTimestampUs = info.timestampUs;
+        lastDurationUs = timestampUs - lastTimestampUs;
+        lastTimestampUs = timestampUs;
 
         if (isSync != 0) {
-            mStssTableEntries.push_back(mSampleInfos.size());
+            mStssTableEntries.push_back(mSampleSizes.size());
         }
 
         if (mTrackingProgressStatus) {
@@ -1178,7 +1183,9 @@
             } else {
                 if (timestampUs - chunkTimestampUs > interleaveDurationUs) {
                     ++nChunks;
-                    mChunkDurations.push_back(timestampUs - chunkTimestampUs);
+                    if (collectStats) {
+                        mChunkDurations.push_back(timestampUs - chunkTimestampUs);
+                    }
                     if (nChunks == 1 ||  // First chunk
                         (--(mStscTableEntries.end()))->samplesPerChunk !=
                          mChunkSamples.size()) {
@@ -1194,14 +1201,14 @@
 
     }
 
-    if (mSampleInfos.empty()) {
+    if (mSampleSizes.empty()) {
         err = UNKNOWN_ERROR;
     }
     mOwner->trackProgressStatus(this, -1, err);
 
     // Last chunk
     if (mOwner->numTracks() == 1) {
-        StscTableEntry stscEntry(1, mSampleInfos.size(), 1);
+        StscTableEntry stscEntry(1, mSampleSizes.size(), 1);
         mStscTableEntries.push_back(stscEntry);
     } else if (!mChunkSamples.empty()) {
         ++nChunks;
@@ -1213,7 +1220,7 @@
     // We don't really know how long the last frame lasts, since
     // there is no frame time after it, just repeat the previous
     // frame's duration.
-    if (mSampleInfos.size() == 1) {
+    if (mSampleSizes.size() == 1) {
         lastDurationUs = 0;  // A single sample's duration
     } else {
         ++sampleCount;  // Count for the last sample
@@ -1222,7 +1229,7 @@
     mSttsTableEntries.push_back(sttsEntry);
     mReachedEOS = true;
     LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames - %s",
-            count, nZeroLengthFrames, mSampleInfos.size(), is_audio? "audio": "video");
+            count, nZeroLengthFrames, mSampleSizes.size(), is_audio? "audio": "video");
 
     logStatisticalData(is_audio);
 }
@@ -1284,8 +1291,8 @@
 
 void MPEG4Writer::Track::findMinAvgMaxSampleDurationMs(
         int32_t *min, int32_t *avg, int32_t *max) {
-    CHECK(!mSampleInfos.empty());
-    int32_t avgSampleDurationMs = mMaxTimeStampUs / 1000 / mSampleInfos.size();
+    CHECK(!mSampleSizes.empty());
+    int32_t avgSampleDurationMs = mMaxTimeStampUs / 1000 / mSampleSizes.size();
     int32_t minSampleDurationMs = 0x7FFFFFFF;
     int32_t maxSampleDurationMs = 0;
     for (List<SttsTableEntry>::iterator it = mSttsTableEntries.begin();
@@ -1327,22 +1334,17 @@
 }
 
 void MPEG4Writer::Track::logStatisticalData(bool isAudio) {
-    if (mMaxTimeStampUs <= 0 || mSampleInfos.empty()) {
+    if (mMaxTimeStampUs <= 0 || mSampleSizes.empty()) {
         LOGI("nothing is recorded");
         return;
     }
 
-    bool collectStats = false;
-    char value[PROPERTY_VALUE_MAX];
-    if (property_get("media.stagefright.record-stats", value, NULL)
-        && (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
-        collectStats = true;
-    }
+    bool collectStats = collectStatisticalData();
 
     if (collectStats) {
         LOGI("%s track - duration %lld us, total %d frames",
                 isAudio? "audio": "video", mMaxTimeStampUs,
-                mSampleInfos.size());
+                mSampleSizes.size());
         int32_t min, avg, max;
         findMinAvgMaxSampleDurationMs(&min, &avg, &max);
         LOGI("min/avg/max sample duration (ms): %d/%d/%d", min, avg, max);
@@ -1355,9 +1357,9 @@
         }
 
         int64_t totalBytes = 0;
-        for (List<SampleInfo>::iterator it = mSampleInfos.begin();
-            it != mSampleInfos.end(); ++it) {
-            totalBytes += it->size;
+        for (List<size_t>::iterator it = mSampleSizes.begin();
+            it != mSampleSizes.end(); ++it) {
+            totalBytes += (*it);
         }
         float bitRate = (totalBytes * 8000000.0) / mMaxTimeStampUs;
         LOGI("avg bit rate (bps): %.2f", bitRate);
@@ -1731,16 +1733,16 @@
           mOwner->beginBox("stsz");
             mOwner->writeInt32(0);  // version=0, flags=0
             if (mSamplesHaveSameSize) {
-                List<SampleInfo>::iterator it = mSampleInfos.begin();
-                mOwner->writeInt32(it->size);  // default sample size
+                List<size_t>::iterator it = mSampleSizes.begin();
+                mOwner->writeInt32(*it);  // default sample size
             } else {
                 mOwner->writeInt32(0);
             }
-            mOwner->writeInt32(mSampleInfos.size());
+            mOwner->writeInt32(mSampleSizes.size());
             if (!mSamplesHaveSameSize) {
-                for (List<SampleInfo>::iterator it = mSampleInfos.begin();
-                     it != mSampleInfos.end(); ++it) {
-                    mOwner->writeInt32((*it).size);
+                for (List<size_t>::iterator it = mSampleSizes.begin();
+                     it != mSampleSizes.end(); ++it) {
+                    mOwner->writeInt32(*it);
                 }
             }
           mOwner->endBox();  // stsz
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 59bf711..a82282d 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -21,14 +21,21 @@
 #include <ui/Input.h>
 #include <ui/InputTransport.h>
 #include <utils/PollLoop.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
 
 #include <android_runtime/android_app_NativeActivity.h>
 
 #include <poll.h>
+#include <errno.h>
 
 using android::InputEvent;
 using android::KeyEvent;
 using android::MotionEvent;
+using android::InputDeviceInfo;
+using android::InputDeviceProxy;
+using android::sp;
+using android::Vector;
 
 int32_t AInputEvent_getType(const AInputEvent* event) {
     return static_cast<const InputEvent*>(event)->getType();
@@ -263,3 +270,74 @@
 void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled) {
     queue->finishEvent(event, handled != 0);
 }
+
+
+int32_t AInputDevice_getDeviceIds(int32_t* idBuf, size_t nMax, size_t* nActual) {
+    Vector<int32_t> ids;
+    InputDeviceProxy::getDeviceIds(ids);
+
+    if (nActual) {
+        *nActual = ids.size();
+    }
+
+    if (idBuf && ids.size() < nMax) {
+        memcpy(idBuf, ids.array(), ids.size() * sizeof(int32_t));
+        return 0;
+    }
+
+    return -ENOMEM;
+}
+
+AInputDevice* AInputDevice_acquire(int32_t deviceId) {
+    sp<InputDeviceProxy> proxy(InputDeviceProxy::getDevice(deviceId));
+    if (proxy == NULL) {
+        return NULL;
+    }
+    proxy->incStrong((void*)AInputDevice_acquire);
+    return static_cast<AInputDevice*>(proxy.get());
+}
+
+void AInputDevice_release(AInputDevice* device) {
+    if (device) {
+        InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device);
+        proxy->decStrong((void*)AInputDevice_acquire);
+    }
+}
+
+const char* AInputDevice_getName(AInputDevice* device) {
+    InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device);
+    return proxy->getInfo()->getName().string();
+}
+
+uint32_t AInputDevice_getSources(AInputDevice* device) {
+    InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device);
+    return proxy->getInfo()->getSources();
+}
+
+int32_t AInputDevice_getKeyboardType(AInputDevice* device) {
+    InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device);
+    return proxy->getInfo()->getKeyboardType();
+}
+
+int32_t AInputDevice_getMotionRange(AInputDevice* device, int32_t rangeType,
+        float* outMin, float* outMax, float* outFlat, float* outFuzz) {
+    InputDeviceProxy* proxy = static_cast<InputDeviceProxy*>(device);
+    const InputDeviceInfo::MotionRange* range = proxy->getInfo()->getMotionRange(rangeType);
+    if (range) {
+        if (outMin) {
+            *outMin = range->min;
+        }
+        if (outMax) {
+            *outMax = range->max;
+        }
+        if (outFlat) {
+            *outFlat = range->flat;
+        }
+        if (outFuzz) {
+            *outFuzz = range->fuzz;
+        }
+        return 0;
+    } else {
+        return -ENOTSUP;
+    }
+}
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 0b8c7e4..9883ac70 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -268,7 +268,6 @@
 /*
  * Input sources.
  *
- * The appropriate interpretation for an input event depends on its source.
  * Refer to the documentation on android.view.InputDevice for more details about input sources
  * and their correct interpretation.
  */
@@ -297,6 +296,37 @@
 };
 
 /*
+ * Keyboard types.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details.
+ */
+enum {
+    AINPUT_KEYBOARD_TYPE_NONE = 0,
+    AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
+    AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
+};
+
+/*
+ * Constants used to retrieve information about the range of motion for a particular
+ * coordinate of a motion event.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details about input sources
+ * and their correct interpretation.
+ */
+enum {
+    AINPUT_MOTION_RANGE_X = 0,
+    AINPUT_MOTION_RANGE_Y = 1,
+    AINPUT_MOTION_RANGE_PRESSURE = 2,
+    AINPUT_MOTION_RANGE_SIZE = 3,
+    AINPUT_MOTION_RANGE_TOUCH_MAJOR = 4,
+    AINPUT_MOTION_RANGE_TOUCH_MINOR = 5,
+    AINPUT_MOTION_RANGE_TOOL_MAJOR = 6,
+    AINPUT_MOTION_RANGE_TOOL_MINOR = 7,
+    AINPUT_MOTION_RANGE_ORIENTATION = 8,
+};
+
+
+/*
  * Input event accessors.
  *
  * Note that most functions can only be used on input events that are of a given type.
@@ -475,7 +505,7 @@
  * upwards, is perfectly circular or is of unknown orientation.  A positive angle
  * indicates that the major axis of contact is oriented to the right.  A negative angle
  * indicates that the major axis of contact is oriented to the left.
- * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
  * (finger pointing fully right). */
 float AMotionEvent_getOrientation(const AInputEvent* motion_event, size_t pointer_index);
 
@@ -575,7 +605,7 @@
  * upwards, is perfectly circular or is of unknown orientation.  A positive angle
  * indicates that the major axis of contact is oriented to the right.  A negative angle
  * indicates that the major axis of contact is oriented to the left.
- * The full range is from -PI/4 radians (finger pointing fully left) to PI/4 radians
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
  * (finger pointing fully right). */
 float AMotionEvent_getHistoricalOrientation(const AInputEvent* motion_event, size_t pointer_index,
         size_t history_index);
@@ -631,6 +661,64 @@
  */
 void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
 
+/*
+ * Input devices.
+ *
+ * These functions provide a mechanism for querying the set of available input devices
+ * and their characteristics and capabilities.
+ */
+struct AInputDevice;
+typedef struct AInputDevice AInputDevice;
+
+/*
+ * Populates the supplied array with the ids of all input devices in the system.
+ * Sets nActual to the actual number of devices.
+ * Returns zero if enumeration was successful.
+ * Returns non-zero if the actual number of devices is greater than nMax, in which case the
+ * client should call the method again with a larger id buffer.
+ */
+int32_t AInputDevice_getDeviceIds(int32_t* idBuf, size_t nMax, size_t* nActual);
+
+/*
+ * Acquires a device by id.
+ * Returns NULL if the device was not found.
+ *
+ * Note: The returned object must be freed using AInputDevice_release when no longer needed.
+ */
+AInputDevice* AInputDevice_acquire(int32_t deviceId);
+
+/*
+ * Releases a device previously acquired by AInputDevice_acquire.
+ * If device is NULL, this function does nothing.
+ */
+void AInputDevice_release(AInputDevice* device);
+
+/*
+ * Gets the name of an input device.
+ *
+ * Note: The caller should copy the name into a private buffer since the returned pointer
+ * will become invalid when the device object is released.
+ */
+const char* AInputDevice_getName(AInputDevice* device);
+
+/*
+ * Gets the combination of input sources provided by the input device.
+ */
+uint32_t AInputDevice_getSources(AInputDevice* device);
+
+/*
+ * Gets the keyboard type.
+ */
+int32_t AInputDevice_getKeyboardType(AInputDevice* device);
+
+/* Gets the minimum value, maximum value, flat position and error tolerance for a
+ * particular motion coodinate.
+ * Returns zero if the device supports the specified motion range. */
+int32_t AInputDevice_getMotionRange(AInputDevice* device, int32_t rangeType,
+        float* outMin, float* outMax, float* outFlat, float* outFuzz);
+
+//TODO hasKey, keymap stuff, etc...
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 5771e85..458ac9d 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -24,12 +24,14 @@
 
 import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuDialogHelper;
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuView;
 import com.android.internal.view.menu.SubMenuBuilder;
+import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.ActionBarView;
 
 import android.app.KeyguardManager;
@@ -196,9 +198,6 @@
             /* Custom title feature is enabled and the user is trying to enable another feature */
             throw new AndroidRuntimeException("You cannot combine custom titles with other title features");
         }
-        if (featureId == FEATURE_OPENGL) {
-            getAttributes().memoryType = WindowManager.LayoutParams.MEMORY_TYPE_GPU;
-        }
         return super.requestFeature(featureId);
     }
 
@@ -1641,6 +1640,9 @@
         private boolean mWatchingForMenu;
         private int mDownY;
 
+        private ActionMode mActionMode;
+        private ActionBarContextView mActionModeView;
+
         public DecorView(Context context, int featureId) {
             super(context);
             mFeatureId = featureId;
@@ -1938,7 +1940,33 @@
 
         @Override
         public ActionMode startActionMode(ActionMode.Callback callback) {
-            return getCallback().onStartActionMode(callback);
+            if (mActionMode != null) {
+                mActionMode.finish();
+            }
+
+            ActionMode mode = getCallback().onStartActionMode(callback);
+            if (mode != null) {
+                mActionMode = mode;
+            } else {
+                if (mActionModeView == null) {
+                    mActionModeView = (ActionBarContextView) findViewById(
+                            com.android.internal.R.id.action_mode_bar);
+                }
+
+                if (mActionModeView != null) {
+                    mode = new StandaloneActionMode(getContext(), mActionModeView,
+                            new ActionModeCallbackWrapper(callback));
+                    if (callback.onCreateActionMode(mode, mode.getMenu())) {
+                        mode.invalidate();
+                        mActionModeView.initForMode(mode);
+                        mActionModeView.setVisibility(View.VISIBLE);
+                        mActionMode = mode;
+                    } else {
+                        mActionMode = null;
+                    }
+                }
+            }
+            return mActionMode;
         }
 
         public void startChanging() {
@@ -2120,6 +2148,35 @@
             if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
             else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
+
+        /**
+         * Clears out internal reference when the action mode is destroyed.
+         */
+        private class ActionModeCallbackWrapper implements ActionMode.Callback {
+            private ActionMode.Callback mWrapped;
+
+            public ActionModeCallbackWrapper(ActionMode.Callback wrapped) {
+                mWrapped = wrapped;
+            }
+
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                return mWrapped.onCreateActionMode(mode, menu);
+            }
+
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                return mWrapped.onPrepareActionMode(mode, menu);
+            }
+
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                return mWrapped.onActionItemClicked(mode, item);
+            }
+
+            public void onDestroyActionMode(ActionMode mode) {
+                mWrapped.onDestroyActionMode(mode);
+                mActionModeView.removeAllViews();
+                mActionMode = null;
+            }
+        }
     }
 
     protected DecorView generateDecor() {
@@ -2173,6 +2230,10 @@
             requestFeature(FEATURE_ACTION_BAR);
         }
 
+        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) {
+            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
+        }
+
         if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
             setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
         }
@@ -2255,13 +2316,7 @@
             if (mIsFloating) {
                 layoutResource = com.android.internal.R.layout.dialog_title;
             } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
-                Configuration config = getContext().getResources().getConfiguration();
-                if ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_XLARGE) ==
-                    Configuration.SCREENLAYOUT_SIZE_XLARGE) {
-                    layoutResource = com.android.internal.R.layout.screen_xlarge_action_bar;
-                } else {
-                    layoutResource = com.android.internal.R.layout.screen_action_bar;
-                }
+                layoutResource = com.android.internal.R.layout.screen_action_bar;
             } else {
                 layoutResource = com.android.internal.R.layout.screen_title;
             }
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index b88e69d..3572d10 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -5408,8 +5408,13 @@
             this, thread.get(), mConfig.inputCfg.buffer.raw, mConfig.inputCfg.buffer.frameCount);
 
     status_t cmdStatus;
-    int size = sizeof(int);
-    status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_CONFIGURE, sizeof(effect_config_t), &mConfig, &size, &cmdStatus);
+    uint32_t size = sizeof(int);
+    status_t status = (*mEffectInterface)->command(mEffectInterface,
+                                                   EFFECT_CMD_CONFIGURE,
+                                                   sizeof(effect_config_t),
+                                                   &mConfig,
+                                                   &size,
+                                                   &cmdStatus);
     if (status == 0) {
         status = cmdStatus;
     }
@@ -5427,8 +5432,13 @@
         return NO_INIT;
     }
     status_t cmdStatus;
-    int size = sizeof(status_t);
-    status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_INIT, 0, NULL, &size, &cmdStatus);
+    uint32_t size = sizeof(status_t);
+    status_t status = (*mEffectInterface)->command(mEffectInterface,
+                                                   EFFECT_CMD_INIT,
+                                                   0,
+                                                   NULL,
+                                                   &size,
+                                                   &cmdStatus);
     if (status == 0) {
         status = cmdStatus;
     }
@@ -5441,8 +5451,13 @@
         return NO_INIT;
     }
     status_t cmdStatus;
-    int size = sizeof(status_t);
-    status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_ENABLE, 0, NULL, &size, &cmdStatus);
+    uint32_t size = sizeof(status_t);
+    status_t status = (*mEffectInterface)->command(mEffectInterface,
+                                                   EFFECT_CMD_ENABLE,
+                                                   0,
+                                                   NULL,
+                                                   &size,
+                                                   &cmdStatus);
     if (status == 0) {
         status = cmdStatus;
     }
@@ -5455,15 +5470,24 @@
         return NO_INIT;
     }
     status_t cmdStatus;
-    int size = sizeof(status_t);
-    status_t status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_DISABLE, 0, NULL, &size, &cmdStatus);
+    uint32_t size = sizeof(status_t);
+    status_t status = (*mEffectInterface)->command(mEffectInterface,
+                                                   EFFECT_CMD_DISABLE,
+                                                   0,
+                                                   NULL,
+                                                   &size,
+                                                   &cmdStatus);
     if (status == 0) {
         status = cmdStatus;
     }
     return status;
 }
 
-status_t AudioFlinger::EffectModule::command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData)
+status_t AudioFlinger::EffectModule::command(uint32_t cmdCode,
+                                             uint32_t cmdSize,
+                                             void *pCmdData,
+                                             uint32_t *replySize,
+                                             void *pReplyData)
 {
     Mutex::Autolock _l(mLock);
 //    LOGV("command(), cmdCode: %d, mEffectInterface: %p", cmdCode, mEffectInterface);
@@ -5471,9 +5495,14 @@
     if (mEffectInterface == NULL) {
         return NO_INIT;
     }
-    status_t status = (*mEffectInterface)->command(mEffectInterface, cmdCode, cmdSize, pCmdData, replySize, pReplyData);
+    status_t status = (*mEffectInterface)->command(mEffectInterface,
+                                                   cmdCode,
+                                                   cmdSize,
+                                                   pCmdData,
+                                                   replySize,
+                                                   pReplyData);
     if (cmdCode != EFFECT_CMD_GET_PARAM && status == NO_ERROR) {
-        int size = (replySize == NULL) ? 0 : *replySize;
+        uint32_t size = (replySize == NULL) ? 0 : *replySize;
         for (size_t i = 1; i < mHandles.size(); i++) {
             sp<EffectHandle> h = mHandles[i].promote();
             if (h != 0) {
@@ -5549,13 +5578,18 @@
         status_t cmdStatus;
         uint32_t volume[2];
         uint32_t *pVolume = NULL;
-        int size = sizeof(volume);
+        uint32_t size = sizeof(volume);
         volume[0] = *left;
         volume[1] = *right;
         if (controller) {
             pVolume = volume;
         }
-        status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_VOLUME, size, volume, &size, pVolume);
+        status = (*mEffectInterface)->command(mEffectInterface,
+                                              EFFECT_CMD_SET_VOLUME,
+                                              size,
+                                              volume,
+                                              &size,
+                                              pVolume);
         if (controller && status == NO_ERROR && size == sizeof(volume)) {
             *left = volume[0];
             *right = volume[1];
@@ -5575,8 +5609,13 @@
             return BAD_VALUE;
         }
         status_t cmdStatus;
-        int size = sizeof(status_t);
-        status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_DEVICE, sizeof(uint32_t), &device, &size, &cmdStatus);
+        uint32_t size = sizeof(status_t);
+        status = (*mEffectInterface)->command(mEffectInterface,
+                                              EFFECT_CMD_SET_DEVICE,
+                                              sizeof(uint32_t),
+                                              &device,
+                                              &size,
+                                              &cmdStatus);
         if (status == NO_ERROR) {
             status = cmdStatus;
         }
@@ -5595,8 +5634,13 @@
             return BAD_VALUE;
         }
         status_t cmdStatus;
-        int size = sizeof(status_t);
-        status = (*mEffectInterface)->command(mEffectInterface, EFFECT_CMD_SET_AUDIO_MODE, sizeof(int), &effectMode, &size, &cmdStatus);
+        uint32_t size = sizeof(status_t);
+        status = (*mEffectInterface)->command(mEffectInterface,
+                                              EFFECT_CMD_SET_AUDIO_MODE,
+                                              sizeof(int),
+                                              &effectMode,
+                                              &size,
+                                              &cmdStatus);
         if (status == NO_ERROR) {
             status = cmdStatus;
         }
@@ -5805,9 +5849,14 @@
     }
 }
 
-status_t AudioFlinger::EffectHandle::command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData)
+status_t AudioFlinger::EffectHandle::command(uint32_t cmdCode,
+                                             uint32_t cmdSize,
+                                             void *pCmdData,
+                                             uint32_t *replySize,
+                                             void *pReplyData)
 {
-//    LOGV("command(), cmdCode: %d, mHasControl: %d, mEffect: %p", cmdCode, mHasControl, (mEffect == 0) ? 0 : mEffect.get());
+//    LOGV("command(), cmdCode: %d, mHasControl: %d, mEffect: %p",
+//              cmdCode, mHasControl, (mEffect == 0) ? 0 : mEffect.get());
 
     // only get parameter command is permitted for applications not controlling the effect
     if (!mHasControl && cmdCode != EFFECT_CMD_GET_PARAM) {
@@ -5829,7 +5878,7 @@
         status_t status = NO_ERROR;
         while (mCblk->serverIndex < mCblk->clientIndex) {
             int reply;
-            int rsize = sizeof(int);
+            uint32_t rsize = sizeof(int);
             int *p = (int *)(mBuffer + mCblk->serverIndex);
             int size = *p++;
             if (((uint8_t *)p + size) > mBuffer + mCblk->clientIndex) {
@@ -5842,8 +5891,14 @@
                 mCblk->serverIndex += size;
                 continue;
             }
-            int psize = sizeof(effect_param_t) + ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) + param->vsize;
-            status_t ret = mEffect->command(EFFECT_CMD_SET_PARAM, psize, p, &rsize, &reply);
+            uint32_t psize = sizeof(effect_param_t) +
+                             ((param->psize - 1) / sizeof(int) + 1) * sizeof(int) +
+                             param->vsize;
+            status_t ret = mEffect->command(EFFECT_CMD_SET_PARAM,
+                                            psize,
+                                            p,
+                                            &rsize,
+                                            &reply);
             if (ret == NO_ERROR) {
                 if (reply != NO_ERROR) {
                     status = reply;
@@ -5879,7 +5934,11 @@
     }
 }
 
-void AudioFlinger::EffectHandle::commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData)
+void AudioFlinger::EffectHandle::commandExecuted(uint32_t cmdCode,
+                                                 uint32_t cmdSize,
+                                                 void *pCmdData,
+                                                 uint32_t replySize,
+                                                 void *pReplyData)
 {
     if (mEffectClient != 0) {
         mEffectClient->commandExecuted(cmdCode, cmdSize, pCmdData, replySize, pReplyData);
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index a8c9a92..8f667a3 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -933,7 +933,11 @@
         int         id() { return mId; }
         void process();
         void updateState();
-        status_t command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData);
+        status_t command(uint32_t cmdCode,
+                         uint32_t cmdSize,
+                         void *pCmdData,
+                         uint32_t *replySize,
+                         void *pReplyData);
 
         void reset_l();
         status_t configure();
@@ -1023,7 +1027,11 @@
         // IEffect
         virtual status_t enable();
         virtual status_t disable();
-        virtual status_t command(int cmdCode, int cmdSize, void *pCmdData, int *replySize, void *pReplyData);
+        virtual status_t command(uint32_t cmdCode,
+                                 uint32_t cmdSize,
+                                 void *pCmdData,
+                                 uint32_t *replySize,
+                                 void *pReplyData);
         virtual void disconnect();
         virtual sp<IMemory> getCblk() const;
         virtual status_t onTransact(uint32_t code, const Parcel& data,
@@ -1032,7 +1040,11 @@
 
         // Give or take control of effect module
         void setControl(bool hasControl, bool signal);
-        void commandExecuted(int cmdCode, int cmdSize, void *pCmdData, int replySize, void *pReplyData);
+        void commandExecuted(uint32_t cmdCode,
+                             uint32_t cmdSize,
+                             void *pCmdData,
+                             uint32_t replySize,
+                             void *pReplyData);
         void setEnabled(bool enabled);
 
         // Getters
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 9c504fe..311efc8 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -88,6 +88,8 @@
      */
     private List mNetRequestersPids[];
 
+    private WifiWatchdogService mWifiWatchdogService;
+
     // priority order of the nettrackers
     // (excluding dynamically set mNetworkPreference)
     // TODO - move mNetworkTypePreference into this
@@ -318,6 +320,9 @@
                 mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
                 wst.startMonitoring();
 
+                //TODO: as part of WWS refactor, create only when needed
+                mWifiWatchdogService = new WifiWatchdogService(context, wst);
+
                 break;
             case ConnectivityManager.TYPE_MOBILE:
                 mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler,
diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java
new file mode 100644
index 0000000..3081ebe
--- /dev/null
+++ b/services/java/com/android/server/CountryDetectorService.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.server;
+
+import java.util.HashMap;
+
+import com.android.server.location.ComprehensiveCountryDetector;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryDetector;
+import android.location.ICountryListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * This class detects the country that the user is in through
+ * {@link ComprehensiveCountryDetector}.
+ *
+ * @hide
+ */
+public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
+
+    /**
+     * The class represents the remote listener, it will also removes itself
+     * from listener list when the remote process was died.
+     */
+    private final class Receiver implements IBinder.DeathRecipient {
+        private final ICountryListener mListener;
+        private final IBinder mKey;
+
+        public Receiver(ICountryListener listener) {
+            mListener = listener;
+            mKey = listener.asBinder();
+        }
+
+        public void binderDied() {
+            removeListener(mKey);
+        }
+
+        @Override
+        public boolean equals(Object otherObj) {
+            if (otherObj instanceof Receiver) {
+                return mKey.equals(((Receiver) otherObj).mKey);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return mKey.hashCode();
+        }
+
+        public ICountryListener getListener() {
+            return mListener;
+        }
+    }
+
+    private final static String TAG = "CountryDetectorService";
+
+    private final HashMap<IBinder, Receiver> mReceivers;
+    private final Context mContext;
+    private ComprehensiveCountryDetector mCountryDetector;
+    private boolean mSystemReady;
+    private Handler mHandler;
+    private CountryListener mLocationBasedDetectorListener;
+
+    public CountryDetectorService(Context context) {
+        super();
+        mReceivers = new HashMap<IBinder, Receiver>();
+        mContext = context;
+    }
+
+    @Override
+    public Country detectCountry() throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        return mCountryDetector.detectCountry();
+    }
+
+    /**
+     * Add the ICountryListener into the listener list.
+     */
+    @Override
+    public void addCountryListener(ICountryListener listener) throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        addListener(listener);
+    }
+
+    /**
+     * Remove the ICountryListener from the listener list.
+     */
+    @Override
+    public void removeCountryListener(ICountryListener listener) throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        removeListener(listener.asBinder());
+    }
+
+    private void addListener(ICountryListener listener) {
+        synchronized (mReceivers) {
+            Receiver r = new Receiver(listener);
+            try {
+                listener.asBinder().linkToDeath(r, 0);
+                mReceivers.put(listener.asBinder(), r);
+                if (mReceivers.size() == 1) {
+                    Slog.d(TAG, "The first listener is added");
+                    setCountryListener(mLocationBasedDetectorListener);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "linkToDeath failed:", e);
+            }
+        }
+    }
+
+    private void removeListener(IBinder key) {
+        synchronized (mReceivers) {
+            mReceivers.remove(key);
+            if (mReceivers.isEmpty()) {
+                setCountryListener(null);
+                Slog.d(TAG, "No listener is left");
+            }
+        }
+    }
+
+
+    protected void notifyReceivers(Country country) {
+        synchronized(mReceivers) {
+            for (Receiver receiver : mReceivers.values()) {
+                try {
+                    receiver.getListener().onCountryDetected(country);
+                } catch (RemoteException e) {
+                    // TODO: Shall we remove the receiver?
+                    Slog.e(TAG, "notifyReceivers failed:", e);
+                }
+            }
+        }
+    }
+
+    void systemReady() {
+        // Shall we wait for the initialization finish.
+        Thread thread = new Thread(this, "CountryDetectorService");
+        thread.start();
+    }
+
+    private void initialize() {
+        mCountryDetector = new ComprehensiveCountryDetector(mContext);
+        mLocationBasedDetectorListener = new CountryListener() {
+            public void onCountryDetected(final Country country) {
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        notifyReceivers(country);
+                    }
+                });
+            }
+        };
+    }
+
+    public void run() {
+        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+        Looper.prepare();
+        mHandler = new Handler();
+        initialize();
+        mSystemReady = true;
+        Looper.loop();
+    }
+
+    protected void setCountryListener(final CountryListener listener) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mCountryDetector.setCountryListener(listener);
+            }
+        });
+    }
+
+    // For testing
+    boolean isSystemReady() {
+        return mSystemReady;
+    }
+}
diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
index 0de11c6..14b7d3e 100644
--- a/services/java/com/android/server/DropBoxManagerService.java
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -690,8 +690,6 @@
         // was lost.  Tombstones are expunged by age (see above).
 
         if (mAllFiles.blocks > mCachedQuotaBlocks) {
-            Slog.i(TAG, "Usage (" + mAllFiles.blocks + ") > Quota (" + mCachedQuotaBlocks + ")");
-
             // Find a fair share amount of space to limit each tag
             int unsqueezed = mAllFiles.blocks, squeezed = 0;
             TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values());
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index b4f46ab..9a1d017 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -66,13 +66,14 @@
     private static native void nativeSetDisplaySize(int displayId, int width, int height);
     private static native void nativeSetDisplayOrientation(int displayId, int rotation);
     
-    private static native int nativeGetScanCodeState(int deviceId, int deviceClasses,
+    private static native int nativeGetScanCodeState(int deviceId, int sourceMask,
             int scanCode);
-    private static native int nativeGetKeyCodeState(int deviceId, int deviceClasses,
+    private static native int nativeGetKeyCodeState(int deviceId, int sourceMask,
             int keyCode);
-    private static native int nativeGetSwitchState(int deviceId, int deviceClasses,
+    private static native int nativeGetSwitchState(int deviceId, int sourceMask,
             int sw);
-    private static native boolean nativeHasKeys(int[] keyCodes, boolean[] keyExists);
+    private static native boolean nativeHasKeys(int deviceId, int sourceMask,
+            int[] keyCodes, boolean[] keyExists);
     private static native void nativeRegisterInputChannel(InputChannel inputChannel);
     private static native void nativeUnregisterInputChannel(InputChannel inputChannel);
     private static native int nativeInjectKeyEvent(KeyEvent event,
@@ -85,20 +86,28 @@
     private static native void nativePreemptInputDispatch();
     private static native String nativeDump();
     
-    // Device class as defined by EventHub.
-    private static final int CLASS_KEYBOARD = 0x00000001;
-    private static final int CLASS_ALPHAKEY = 0x00000002;
-    private static final int CLASS_TOUCHSCREEN = 0x00000004;
-    private static final int CLASS_TRACKBALL = 0x00000008;
-    private static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
-    private static final int CLASS_DPAD = 0x00000020;
-    
     // Input event injection constants defined in InputDispatcher.h.
     static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
     static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1;
     static final int INPUT_EVENT_INJECTION_FAILED = 2;
     static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3;
     
+    // Key states (may be returned by queries about the current state of a
+    // particular key code, scan code or switch).
+    
+    /** The key state is unknown or the requested key itself is not supported. */
+    public static final int KEY_STATE_UNKNOWN = -1;
+
+    /** The key is up. /*/
+    public static final int KEY_STATE_UP = 0;
+
+    /** The key is down. */
+    public static final int KEY_STATE_DOWN = 1;
+
+    /** The key is down but is a virtual key press that is being emulated by the system. */
+    public static final int KEY_STATE_VIRTUAL = 2;
+
+
     public InputManager(Context context, WindowManagerService windowManagerService) {
         this.mContext = context;
         this.mWindowManagerService = windowManagerService;
@@ -150,55 +159,67 @@
         config.navigation = mNavigationConfig;
     }
     
-    public int getScancodeState(int code) {
-        return nativeGetScanCodeState(0, -1, code);
+    /**
+     * Gets the current state of a key or button by key code.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param keyCode The key code to check.
+     * @return The key state.
+     */
+    public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) {
+        return nativeGetKeyCodeState(deviceId, sourceMask, keyCode);
     }
     
-    public int getScancodeState(int deviceId, int code) {
-        return nativeGetScanCodeState(deviceId, -1, code);
+    /**
+     * Gets the current state of a key or button by scan code.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param scanCode The scan code to check.
+     * @return The key state.
+     */
+    public int getScanCodeState(int deviceId, int sourceMask, int scanCode) {
+        return nativeGetScanCodeState(deviceId, sourceMask, scanCode);
     }
     
-    public int getTrackballScancodeState(int code) {
-        return nativeGetScanCodeState(-1, CLASS_TRACKBALL, code);
-    }
-    
-    public int getDPadScancodeState(int code) {
-        return nativeGetScanCodeState(-1, CLASS_DPAD, code);
-    }
-    
-    public int getKeycodeState(int code) {
-        return nativeGetKeyCodeState(0, -1, code);
-    }
-    
-    public int getKeycodeState(int deviceId, int code) {
-        return nativeGetKeyCodeState(deviceId, -1, code);
-    }
-    
-    public int getTrackballKeycodeState(int code) {
-        return nativeGetKeyCodeState(-1, CLASS_TRACKBALL, code);
-    }
-    
-    public int getDPadKeycodeState(int code) {
-        return nativeGetKeyCodeState(-1, CLASS_DPAD, code);
+    /**
+     * Gets the current state of a switch by switch code.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param switchCode The switch code to check.
+     * @return The switch state.
+     */
+    public int getSwitchState(int deviceId, int sourceMask, int switchCode) {
+        return nativeGetSwitchState(deviceId, sourceMask, switchCode);
     }
 
-    public int getSwitchState(int sw) {
-        return nativeGetSwitchState(-1, -1, sw);
-    }
-    
-    public int getSwitchState(int deviceId, int sw) {
-        return nativeGetSwitchState(deviceId, -1, sw);
-    }
-
-    public boolean hasKeys(int[] keyCodes, boolean[] keyExists) {
+    /**
+     * Determines whether the specified key codes are supported by a particular device.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param keyCodes The array of key codes to check.
+     * @param keyExists An array at least as large as keyCodes whose entries will be set
+     * to true or false based on the presence or absence of support for the corresponding
+     * key codes.
+     * @return True if the lookup was successful, false otherwise.
+     */
+    public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) {
         if (keyCodes == null) {
             throw new IllegalArgumentException("keyCodes must not be null.");
         }
-        if (keyExists == null) {
-            throw new IllegalArgumentException("keyExists must not be null.");
+        if (keyExists == null || keyExists.length < keyCodes.length) {
+            throw new IllegalArgumentException("keyExists must not be null and must be at "
+                    + "least as large as keyCodes.");
         }
         
-        return nativeHasKeys(keyCodes, keyExists);
+        return nativeHasKeys(deviceId, sourceMask, keyCodes, keyExists);
     }
     
     public void registerInputChannel(InputChannel inputChannel) {
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 01376eb..34b1fc5 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -187,7 +187,9 @@
     static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
             "com.android.defcontainer",
             "com.android.defcontainer.DefaultContainerService");
-    
+
+    static final String mTempContainerPrefix = "smdl2tmp";
+
     final HandlerThread mHandlerThread = new HandlerThread("PackageManager",
             Process.THREAD_PRIORITY_BACKGROUND);
     final PackageHandler mHandler;
@@ -9549,48 +9551,28 @@
         }
     }
 
-   static String getTempContainerId() {
-       String prefix = "smdl2tmp";
-       int tmpIdx = 1;
-       String list[] = PackageHelper.getSecureContainerList();
-       if (list != null) {
-           int idx = 0;
-           int idList[] = new int[MAX_CONTAINERS];
-           boolean neverFound = true;
-           for (String name : list) {
-               // Ignore null entries
-               if (name == null) {
-                   continue;
-               }
-               int sidx = name.indexOf(prefix);
-               if (sidx == -1) {
-                   // Not a temp file. just ignore
-                   continue;
-               }
-               String subStr = name.substring(sidx + prefix.length());
-               idList[idx] = -1;
-               if (subStr != null) {
-                   try {
-                       int cid = Integer.parseInt(subStr);
-                       idList[idx++] = cid;
-                       neverFound = false;
-                   } catch (NumberFormatException e) {
-                   }
-               }
-           }
-           if (!neverFound) {
-               // Sort idList
-               Arrays.sort(idList);
-               for (int j = 1; j <= idList.length; j++) {
-                   if (idList[j-1] != j) {
-                       tmpIdx = j;
-                       break;
-                   }
-               }
-           }
-       }
-       return prefix + tmpIdx;
-   }
+    /* package */ static String getTempContainerId() {
+        int tmpIdx = 1;
+        String list[] = PackageHelper.getSecureContainerList();
+        if (list != null) {
+            for (final String name : list) {
+                // Ignore null and non-temporary container entries
+                if (name == null || !name.startsWith(mTempContainerPrefix)) {
+                    continue;
+                }
+
+                String subStr = name.substring(mTempContainerPrefix.length());
+                try {
+                    int cid = Integer.parseInt(subStr);
+                    if (cid >= tmpIdx) {
+                        tmpIdx = cid + 1;
+                    }
+                } catch (NumberFormatException e) {
+                }
+            }
+        }
+        return mTempContainerPrefix + tmpIdx;
+    }
 
    /*
     * Update media status on PackageManager.
@@ -9806,10 +9788,15 @@
        if (doGc) {
            Runtime.getRuntime().gc();
        }
-       // List stale containers.
+       // List stale containers and destroy stale temporary containers.
        if (removeCids != null) {
            for (String cid : removeCids) {
-               Log.w(TAG, "Container " + cid + " is stale");
+               if (cid.startsWith(mTempContainerPrefix)) {
+                   Log.i(TAG, "Destroying stale temporary container " + cid);
+                   PackageHelper.destroySdDir(cid);
+               } else {
+                   Log.w(TAG, "Container " + cid + " is stale");
+               }
            }
        }
    }
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index ad910c4..2fb481c 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -1052,6 +1052,7 @@
                         case SCREEN_DIM:
                             if (mDimDelay >= 0) {
                                 when = now + mDimDelay;
+                                break;
                             } else {
                                 Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim");
                             }
@@ -1060,6 +1061,9 @@
                                 when = now + mScreenOffDelay;
                             }
                             break;
+                        default:
+                            when = now;
+                            break;
                     }
                 } else {
                     override: {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3586d21..2412b7d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -206,6 +206,7 @@
         NotificationManagerService notification = null;
         WallpaperManagerService wallpaper = null;
         LocationManagerService location = null;
+        CountryDetectorService countryDetector = null;
 
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
             try {
@@ -316,6 +317,14 @@
             }
 
             try {
+                Slog.i(TAG, "Country Detector");
+                countryDetector = new CountryDetectorService(context);
+                ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting Country Detector", e);
+            }
+
+            try {
                 Slog.i(TAG, "Search Service");
                 ServiceManager.addService(Context.SEARCH_SERVICE,
                         new SearchManagerService(context));
@@ -479,6 +488,7 @@
         final InputMethodManagerService immF = imm;
         final RecognitionManagerService recognitionF = recognition;
         final LocationManagerService locationF = location;
+        final CountryDetectorService countryDetectorF = countryDetector;
 
         // We now tell the activity manager it is okay to run third party
         // code.  It will call back into us once it has gotten to the state
@@ -506,6 +516,7 @@
                 if (wallpaperF != null) wallpaperF.systemReady();
                 if (immF != null) immF.systemReady();
                 if (locationF != null) locationF.systemReady();
+                if (countryDetectorF != null) countryDetectorF.systemReady();
                 if (throttleF != null) throttleF.systemReady();
             }
         });
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 35baaa7..15080b2 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,18 +16,6 @@
 
 package com.android.server;
 
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
-
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
-
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothA2dp;
@@ -41,7 +29,6 @@
 import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiNative;
 import android.net.wifi.WifiStateTracker;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
@@ -51,56 +38,43 @@
 import android.net.InterfaceConfiguration;
 import android.net.NetworkStateTracker;
 import android.net.DhcpInfo;
-import android.net.NetworkUtils;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
-import android.text.TextUtils;
 
 import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.regex.Pattern;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.net.UnknownHostException;
 
 import com.android.internal.app.IBatteryStats;
-import android.app.backup.IBackupManager;
 import com.android.server.am.BatteryStatsService;
 import com.android.internal.R;
 
 /**
  * WifiService handles remote WiFi operation requests by implementing
- * the IWifiManager interface. It also creates a WifiMonitor to listen
- * for Wifi-related events.
+ * the IWifiManager interface.
  *
  * @hide
  */
+//TODO: Clean up multiple locks and implement WifiService
+// as a SM to track soft AP/client/adhoc bring up based
+// on device idle state, airplane mode and boot.
+
 public class WifiService extends IWifiManager.Stub {
     private static final String TAG = "WifiService";
-    private static final boolean DBG = false;
-    private static final Pattern scanResultPattern = Pattern.compile("\t+");
+    private static final boolean DBG = true;
+
     private final WifiStateTracker mWifiStateTracker;
-    /* TODO: fetch a configurable interface */
-    private static final String SOFTAP_IFACE = "wl0.1";
 
     private Context mContext;
-    private int mWifiApState;
 
     private AlarmManager mAlarmManager;
     private PendingIntent mIdleIntent;
@@ -109,10 +83,8 @@
     private boolean mDeviceIdle;
     private int mPluggedType;
 
-    private enum DriverAction {DRIVER_UNLOAD, NO_DRIVER_UNLOAD};
-
     // true if the user enabled Wifi while in airplane mode
-    private boolean mAirplaneModeOverwridden;
+    private AtomicBoolean mAirplaneModeOverwridden = new AtomicBoolean(false);
 
     private final LockList mLocks = new LockList();
     // some wifi lock statistics
@@ -128,9 +100,7 @@
 
     private final IBatteryStats mBatteryStats;
 
-    private INetworkManagementService nwService;
     ConnectivityManager mCm;
-    private WifiWatchdogService mWifiWatchdogService = null;
     private String[] mWifiRegexs;
 
     /**
@@ -142,46 +112,6 @@
      */
     private static final long DEFAULT_IDLE_MILLIS = 15 * 60 * 1000; /* 15 minutes */
 
-    private static final String WAKELOCK_TAG = "WifiService";
-
-    // Wake lock used by other operations
-    private static PowerManager.WakeLock sWakeLock;
-
-    private static final int MESSAGE_ENABLE_WIFI        = 0;
-    private static final int MESSAGE_DISABLE_WIFI       = 1;
-    private static final int MESSAGE_STOP_WIFI          = 2;
-    private static final int MESSAGE_START_WIFI         = 3;
-    private static final int MESSAGE_UPDATE_STATE       = 5;
-    private static final int MESSAGE_START_ACCESS_POINT = 6;
-    private static final int MESSAGE_STOP_ACCESS_POINT  = 7;
-    private static final int MESSAGE_SET_CHANNELS       = 8;
-
-
-    private final  WifiHandler mWifiHandler;
-
-    /*
-     * Cache of scan results objects (size is somewhat arbitrary)
-     */
-    private static final int SCAN_RESULT_CACHE_SIZE = 80;
-    private final LinkedHashMap<String, ScanResult> mScanResultCache;
-
-    /*
-     * Character buffer used to parse scan results (optimization)
-     */
-    private static final int SCAN_RESULT_BUFFER_SIZE = 512;
-    private boolean mNeedReconfig;
-
-    /*
-     * Last UID that asked to enable WIFI.
-     */
-    private int mLastEnableUid = Process.myUid();
-
-    /*
-     * Last UID that asked to enable WIFI AP.
-     */
-    private int mLastApEnableUid = Process.myUid();
-
-
     /**
      * Number of allowed radio frequency channels in various regulatory domains.
      * This list is sufficient for 802.11b/g networks (2.4GHz range).
@@ -191,46 +121,27 @@
     private static final String ACTION_DEVICE_IDLE =
             "com.android.server.WifiManager.action.DEVICE_IDLE";
 
+    private boolean mIsReceiverRegistered = false;
+
     WifiService(Context context, WifiStateTracker tracker) {
         mContext = context;
         mWifiStateTracker = tracker;
         mWifiStateTracker.enableRssiPolling(true);
         mBatteryStats = BatteryStatsService.getService();
 
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        nwService = INetworkManagementService.Stub.asInterface(b);
-
-        mScanResultCache = new LinkedHashMap<String, ScanResult>(
-            SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
-                /*
-                 * Limit the cache size by SCAN_RESULT_CACHE_SIZE
-                 * elements
-                 */
-                public boolean removeEldestEntry(Map.Entry eldest) {
-                    return SCAN_RESULT_CACHE_SIZE < this.size();
-                }
-            };
-
-        HandlerThread wifiThread = new HandlerThread("WifiService");
-        wifiThread.start();
-        mWifiHandler = new WifiHandler(wifiThread.getLooper());
-
-        mWifiStateTracker.setWifiState(WIFI_STATE_DISABLED);
-        mWifiApState = WIFI_AP_STATE_DISABLED;
-
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
         Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
         mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
 
-        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
-        sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+        HandlerThread wifiThread = new HandlerThread("WifiService");
+        wifiThread.start();
 
         mContext.registerReceiver(
                 new BroadcastReceiver() {
                     @Override
                     public void onReceive(Context context, Intent intent) {
                         // clear our flag indicating the user has overwridden airplane mode
-                        mAirplaneModeOverwridden = false;
+                        mAirplaneModeOverwridden.set(false);
                         // on airplane disable, restore Wifi if the saved state indicates so
                         if (!isAirplaneModeOn() && testAndClearWifiSavedState()) {
                             persistWifiEnabled(true);
@@ -245,11 +156,11 @@
                 @Override
                 public void onReceive(Context context, Intent intent) {
 
-                  ArrayList<String> available = intent.getStringArrayListExtra(
-                          ConnectivityManager.EXTRA_AVAILABLE_TETHER);
-                  ArrayList<String> active = intent.getStringArrayListExtra(
-                          ConnectivityManager.EXTRA_ACTIVE_TETHER);
-                  updateTetherState(available, active);
+                    ArrayList<String> available = intent.getStringArrayListExtra(
+                            ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+                    ArrayList<String> active = intent.getStringArrayListExtra(
+                            ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                    updateTetherState(available, active);
 
                 }
             },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
@@ -301,17 +212,14 @@
                         }
                     } catch (Exception e) {
                         Slog.e(TAG, "Error configuring interface " + intf + ", :" + e);
-                        try {
-                            nwService.stopAccessPoint();
-                        } catch (Exception ee) {
-                            Slog.e(TAG, "Could not stop AP, :" + ee);
-                        }
-                        setWifiApEnabledState(WIFI_AP_STATE_FAILED, 0, DriverAction.DRIVER_UNLOAD);
+                        setWifiApEnabled(null, false);
                         return;
                     }
 
                     if(mCm.tether(intf) != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                        Slog.e(TAG, "Error tethering "+intf);
+                        Slog.e(TAG, "Error tethering on " + intf);
+                        setWifiApEnabled(null, false);
+                        return;
                     }
                     break;
                 }
@@ -353,12 +261,11 @@
 
     /**
      * see {@link android.net.wifi.WifiManager#pingSupplicant()}
-     * @return {@code true} if the operation succeeds
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
     public boolean pingSupplicant() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.ping();
+        enforceAccessPermission();
+        return mWifiStateTracker.pingSupplicant();
     }
 
     /**
@@ -367,170 +274,7 @@
      */
     public boolean startScan(boolean forceActive) {
         enforceChangePermission();
-
-        switch (mWifiStateTracker.getSupplicantState()) {
-            case DISCONNECTED:
-            case INACTIVE:
-            case SCANNING:
-            case DORMANT:
-                break;
-            default:
-                mWifiStateTracker.setScanResultHandling(
-                        WifiStateTracker.SUPPL_SCAN_HANDLING_LIST_ONLY);
-                break;
-        }
-        return mWifiStateTracker.scan(forceActive);
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
-     * @param enable {@code true} to enable, {@code false} to disable.
-     * @return {@code true} if the enable/disable operation was
-     *         started or is already in the queue.
-     */
-    public boolean setWifiEnabled(boolean enable) {
-        enforceChangePermission();
-        if (mWifiHandler == null) return false;
-
-        synchronized (mWifiHandler) {
-            // caller may not have WAKE_LOCK permission - it's not required here
-            long ident = Binder.clearCallingIdentity();
-            sWakeLock.acquire();
-            Binder.restoreCallingIdentity(ident);
-
-            mLastEnableUid = Binder.getCallingUid();
-            // set a flag if the user is enabling Wifi while in airplane mode
-            mAirplaneModeOverwridden = (enable && isAirplaneModeOn() && isAirplaneToggleable());
-            sendEnableMessage(enable, true, Binder.getCallingUid());
-        }
-
-        return true;
-    }
-
-    /**
-     * Enables/disables Wi-Fi synchronously.
-     * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
-     * @param persist {@code true} if the setting should be persisted.
-     * @param uid The UID of the process making the request.
-     * @return {@code true} if the operation succeeds (or if the existing state
-     *         is the same as the requested state)
-     */
-    private boolean setWifiEnabledBlocking(boolean enable, boolean persist, int uid) {
-        final int eventualWifiState = enable ? WIFI_STATE_ENABLED : WIFI_STATE_DISABLED;
-        final int wifiState = mWifiStateTracker.getWifiState();
-
-        if (wifiState == eventualWifiState) {
-            return true;
-        }
-        if (enable && isAirplaneModeOn() && !mAirplaneModeOverwridden) {
-            return false;
-        }
-
-        /**
-         * Multiple calls to unregisterReceiver() cause exception and a system crash.
-         * This can happen if a supplicant is lost (or firmware crash occurs) and user indicates
-         * disable wifi at the same time.
-         * Avoid doing a disable when the current Wifi state is UNKNOWN
-         * TODO: Handle driver load fail and supplicant lost as seperate states
-         */
-        if ((wifiState == WIFI_STATE_UNKNOWN) && !enable) {
-            return false;
-        }
-
-        /**
-         * Fail Wifi if AP is enabled
-         * TODO: Deprecate WIFI_STATE_UNKNOWN and rename it
-         * WIFI_STATE_FAILED
-         */
-        if ((mWifiApState == WIFI_AP_STATE_ENABLED) && enable) {
-            setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-            return false;
-        }
-
-        setWifiEnabledState(enable ? WIFI_STATE_ENABLING : WIFI_STATE_DISABLING, uid);
-
-        if (enable) {
-            if (!mWifiStateTracker.loadDriver()) {
-                Slog.e(TAG, "Failed to load Wi-Fi driver.");
-                setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                return false;
-            }
-            if (!mWifiStateTracker.startSupplicant()) {
-                mWifiStateTracker.unloadDriver();
-                Slog.e(TAG, "Failed to start supplicant daemon.");
-                setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                return false;
-            }
-
-            registerForBroadcasts();
-            mWifiStateTracker.startEventLoop();
-
-        } else {
-
-            mContext.unregisterReceiver(mReceiver);
-           // Remove notification (it will no-op if it isn't visible)
-            mWifiStateTracker.setNotificationVisible(false, 0, false, 0);
-
-            boolean failedToStopSupplicantOrUnloadDriver = false;
-
-            if (!mWifiStateTracker.stopSupplicant()) {
-                Slog.e(TAG, "Failed to stop supplicant daemon.");
-                setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                failedToStopSupplicantOrUnloadDriver = true;
-            }
-
-            /**
-             * Reset connections and disable interface
-             * before we unload the driver
-             */
-            mWifiStateTracker.resetConnections(true);
-
-            if (!mWifiStateTracker.unloadDriver()) {
-                Slog.e(TAG, "Failed to unload Wi-Fi driver.");
-                if (!failedToStopSupplicantOrUnloadDriver) {
-                    setWifiEnabledState(WIFI_STATE_UNKNOWN, uid);
-                    failedToStopSupplicantOrUnloadDriver = true;
-                }
-            }
-
-            if (failedToStopSupplicantOrUnloadDriver) {
-                return false;
-            }
-        }
-
-        // Success!
-
-        if (persist) {
-            persistWifiEnabled(enable);
-        }
-        setWifiEnabledState(eventualWifiState, uid);
-        return true;
-    }
-
-    private void setWifiEnabledState(int wifiState, int uid) {
-        final int previousWifiState = mWifiStateTracker.getWifiState();
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            if (wifiState == WIFI_STATE_ENABLED) {
-                mBatteryStats.noteWifiOn(uid);
-            } else if (wifiState == WIFI_STATE_DISABLED) {
-                mBatteryStats.noteWifiOff(uid);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        // Update state
-        mWifiStateTracker.setWifiState(wifiState);
-
-        // Broadcast
-        final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
-        mContext.sendStickyBroadcast(intent);
+        return mWifiStateTracker.startScan(forceActive);
     }
 
     private void enforceAccessPermission() {
@@ -551,6 +295,40 @@
     }
 
     /**
+     * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public synchronized boolean setWifiEnabled(boolean enable) {
+        enforceChangePermission();
+
+        if (DBG) {
+            Slog.e(TAG, "Invoking mWifiStateTracker.setWifiEnabled\n");
+        }
+
+        // set a flag if the user is enabling Wifi while in airplane mode
+        if (enable && isAirplaneModeOn() && isAirplaneToggleable()) {
+            mAirplaneModeOverwridden.set(true);
+        }
+
+        mWifiStateTracker.setWifiEnabled(enable);
+        persistWifiEnabled(enable);
+
+        if (enable) {
+            if (!mIsReceiverRegistered) {
+                registerForBroadcasts();
+                mIsReceiverRegistered = true;
+            }
+        } else if (mIsReceiverRegistered){
+            mContext.unregisterReceiver(mReceiver);
+            mIsReceiverRegistered = false;
+        }
+
+        return true;
+    }
+
+    /**
      * see {@link WifiManager#getWifiState()}
      * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
      *         {@link WifiManager#WIFI_STATE_DISABLING},
@@ -564,62 +342,49 @@
     }
 
     /**
-     * see {@link android.net.wifi.WifiManager#disconnect()}
-     * @return {@code true} if the operation succeeds
-     */
-    public boolean disconnect() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.disconnect();
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#reconnect()}
-     * @return {@code true} if the operation succeeds
-     */
-    public boolean reconnect() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.reconnectCommand();
-    }
-
-    /**
-     * see {@link android.net.wifi.WifiManager#reassociate()}
-     * @return {@code true} if the operation succeeds
-     */
-    public boolean reassociate() {
-        enforceChangePermission();
-
-        return mWifiStateTracker.reassociate();
-    }
-
-    /**
      * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
      * @param wifiConfig SSID, security and channel details as
      *        part of WifiConfiguration
-     * @param enabled, true to enable and false to disable
+     * @param enabled true to enable and false to disable
      * @return {@code true} if the start operation was
      *         started or is already in the queue.
      */
-    public boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+    public synchronized boolean setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
         enforceChangePermission();
-        if (mWifiHandler == null) return false;
 
-        synchronized (mWifiHandler) {
-
-            long ident = Binder.clearCallingIdentity();
-            sWakeLock.acquire();
-            Binder.restoreCallingIdentity(ident);
-
-            mLastApEnableUid = Binder.getCallingUid();
-            sendAccessPointMessage(enabled, wifiConfig, Binder.getCallingUid());
+        if (enabled) {
+            /* Use default config if there is no existing config */
+            if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
+                wifiConfig = new WifiConfiguration();
+                wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
+                wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
+            }
+            setWifiApConfiguration(wifiConfig);
         }
 
+        mWifiStateTracker.setWifiApEnabled(wifiConfig, enabled);
+
         return true;
     }
 
-    public WifiConfiguration getWifiApConfiguration() {
+    /**
+     * see {@link WifiManager#getWifiApState()}
+     * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     */
+    public int getWifiApEnabledState() {
         enforceAccessPermission();
+        return mWifiStateTracker.getWifiApState();
+    }
+
+    /**
+     * see {@link WifiManager#getWifiApConfiguration()}
+     * @return soft access point configuration
+     */
+    public synchronized WifiConfiguration getWifiApConfiguration() {
         final ContentResolver cr = mContext.getContentResolver();
         WifiConfiguration wifiConfig = new WifiConfiguration();
         int authType;
@@ -637,7 +402,11 @@
         }
     }
 
-    public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+    /**
+     * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
+     * @param wifiConfig WifiConfiguration details for soft access point
+     */
+    public synchronized void setWifiApConfiguration(WifiConfiguration wifiConfig) {
         enforceChangePermission();
         final ContentResolver cr = mContext.getContentResolver();
         boolean isWpa;
@@ -653,143 +422,30 @@
     }
 
     /**
-     * Enables/disables Wi-Fi AP synchronously. The driver is loaded
-     * and soft access point configured as a single operation.
-     * @param enable {@code true} to turn Wi-Fi on, {@code false} to turn it off.
-     * @param uid The UID of the process making the request.
-     * @param wifiConfig The WifiConfiguration for AP
-     * @return {@code true} if the operation succeeds (or if the existing state
-     *         is the same as the requested state)
+     * see {@link android.net.wifi.WifiManager#disconnect()}
+     * @return {@code true} if the operation succeeds
      */
-    private boolean setWifiApEnabledBlocking(boolean enable,
-                                int uid, WifiConfiguration wifiConfig) {
-        final int eventualWifiApState = enable ? WIFI_AP_STATE_ENABLED : WIFI_AP_STATE_DISABLED;
-
-        if (mWifiApState == eventualWifiApState) {
-            /* Configuration changed on a running access point */
-            if(enable && (wifiConfig != null)) {
-                try {
-                    nwService.setAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(),
-                                             SOFTAP_IFACE);
-                    setWifiApConfiguration(wifiConfig);
-                    return true;
-                } catch(Exception e) {
-                    Slog.e(TAG, "Exception in nwService during AP restart");
-                    try {
-                        nwService.stopAccessPoint();
-                    } catch (Exception ee) {
-                        Slog.e(TAG, "Could not stop AP, :" + ee);
-                    }
-                    setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
-                    return false;
-                }
-            } else {
-                return true;
-            }
-        }
-
-        /**
-         * Fail AP if Wifi is enabled
-         */
-        if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLED) && enable) {
-            setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
-            return false;
-        }
-
-        setWifiApEnabledState(enable ? WIFI_AP_STATE_ENABLING :
-                                       WIFI_AP_STATE_DISABLING, uid, DriverAction.NO_DRIVER_UNLOAD);
-
-        if (enable) {
-
-            /* Use default config if there is no existing config */
-            if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) {
-                wifiConfig = new WifiConfiguration();
-                wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default);
-                wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE);
-            }
-
-            if (!mWifiStateTracker.loadDriver()) {
-                Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
-                return false;
-            }
-
-            try {
-                nwService.startAccessPoint(wifiConfig, mWifiStateTracker.getInterfaceName(),
-                                           SOFTAP_IFACE);
-            } catch(Exception e) {
-                Slog.e(TAG, "Exception in startAccessPoint()");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
-                return false;
-            }
-
-            setWifiApConfiguration(wifiConfig);
-
-        } else {
-
-            try {
-                nwService.stopAccessPoint();
-            } catch(Exception e) {
-                Slog.e(TAG, "Exception in stopAccessPoint()");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.DRIVER_UNLOAD);
-                return false;
-            }
-
-            if (!mWifiStateTracker.unloadDriver()) {
-                Slog.e(TAG, "Failed to unload Wi-Fi driver for AP mode");
-                setWifiApEnabledState(WIFI_AP_STATE_FAILED, uid, DriverAction.NO_DRIVER_UNLOAD);
-                return false;
-            }
-        }
-
-        setWifiApEnabledState(eventualWifiApState, uid, DriverAction.NO_DRIVER_UNLOAD);
-        return true;
+    public boolean disconnect() {
+        enforceChangePermission();
+        return mWifiStateTracker.disconnectCommand();
     }
 
     /**
-     * see {@link WifiManager#getWifiApState()}
-     * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     * see {@link android.net.wifi.WifiManager#reconnect()}
+     * @return {@code true} if the operation succeeds
      */
-    public int getWifiApEnabledState() {
-        enforceAccessPermission();
-        return mWifiApState;
+    public boolean reconnect() {
+        enforceChangePermission();
+        return mWifiStateTracker.reconnectCommand();
     }
 
-    private void setWifiApEnabledState(int wifiAPState, int uid, DriverAction flag) {
-        final int previousWifiApState = mWifiApState;
-
-        /**
-         * Unload the driver if going to a failed state
-         */
-        if ((mWifiApState == WIFI_AP_STATE_FAILED) && (flag == DriverAction.DRIVER_UNLOAD)) {
-            mWifiStateTracker.unloadDriver();
-        }
-
-        long ident = Binder.clearCallingIdentity();
-        try {
-            if (wifiAPState == WIFI_AP_STATE_ENABLED) {
-                mBatteryStats.noteWifiOn(uid);
-            } else if (wifiAPState == WIFI_AP_STATE_DISABLED) {
-                mBatteryStats.noteWifiOff(uid);
-            }
-        } catch (RemoteException e) {
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-
-        // Update state
-        mWifiApState = wifiAPState;
-
-        // Broadcast
-        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiAPState);
-        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
-        mContext.sendStickyBroadcast(intent);
+    /**
+     * see {@link android.net.wifi.WifiManager#reassociate()}
+     * @return {@code true} if the operation succeeds
+     */
+    public boolean reassociate() {
+        enforceChangePermission();
+        return mWifiStateTracker.reassociateCommand();
     }
 
     /**
@@ -798,217 +454,7 @@
      */
     public List<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
-        String listStr;
-
-        /*
-         * We don't cache the list, because we want to allow
-         * for the possibility that the configuration file
-         * has been modified through some external means,
-         * such as the wpa_cli command line program.
-         */
-        listStr = mWifiStateTracker.listNetworks();
-
-        List<WifiConfiguration> networks =
-            new ArrayList<WifiConfiguration>();
-        if (listStr == null)
-            return networks;
-
-        String[] lines = listStr.split("\n");
-        // Skip the first line, which is a header
-       for (int i = 1; i < lines.length; i++) {
-           String[] result = lines[i].split("\t");
-           // network-id | ssid | bssid | flags
-           WifiConfiguration config = new WifiConfiguration();
-           try {
-               config.networkId = Integer.parseInt(result[0]);
-           } catch(NumberFormatException e) {
-               continue;
-           }
-           if (result.length > 3) {
-               if (result[3].indexOf("[CURRENT]") != -1)
-                   config.status = WifiConfiguration.Status.CURRENT;
-               else if (result[3].indexOf("[DISABLED]") != -1)
-                   config.status = WifiConfiguration.Status.DISABLED;
-               else
-                   config.status = WifiConfiguration.Status.ENABLED;
-           } else {
-               config.status = WifiConfiguration.Status.ENABLED;
-           }
-           readNetworkVariables(config);
-           networks.add(config);
-       }
-
-        return networks;
-    }
-
-    /**
-     * Read the variables from the supplicant daemon that are needed to
-     * fill in the WifiConfiguration object.
-     * <p/>
-     * The caller must hold the synchronization monitor.
-     * @param config the {@link WifiConfiguration} object to be filled in.
-     */
-    private void readNetworkVariables(WifiConfiguration config) {
-
-        int netId = config.networkId;
-        if (netId < 0)
-            return;
-
-        /*
-         * TODO: maybe should have a native method that takes an array of
-         * variable names and returns an array of values. But we'd still
-         * be doing a round trip to the supplicant daemon for each variable.
-         */
-        String value;
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.ssidVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.SSID = value;
-        } else {
-            config.SSID = null;
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.bssidVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.BSSID = value;
-        } else {
-            config.BSSID = null;
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.priorityVarName);
-        config.priority = -1;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.priority = Integer.parseInt(value);
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.hiddenSSIDVarName);
-        config.hiddenSSID = false;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.hiddenSSID = Integer.parseInt(value) != 0;
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepTxKeyIdxVarName);
-        config.wepTxKeyIndex = -1;
-        if (!TextUtils.isEmpty(value)) {
-            try {
-                config.wepTxKeyIndex = Integer.parseInt(value);
-            } catch (NumberFormatException ignore) {
-            }
-        }
-
-        /*
-         * Get up to 4 WEP keys. Note that the actual keys are not passed back,
-         * just a "*" if the key is set, or the null string otherwise.
-         */
-        for (int i = 0; i < 4; i++) {
-            value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.wepKeyVarNames[i]);
-            if (!TextUtils.isEmpty(value)) {
-                config.wepKeys[i] = value;
-            } else {
-                config.wepKeys[i] = null;
-            }
-        }
-
-        /*
-         * Get the private shared key. Note that the actual keys are not passed back,
-         * just a "*" if the key is set, or the null string otherwise.
-         */
-        value = mWifiStateTracker.getNetworkVariable(netId, WifiConfiguration.pskVarName);
-        if (!TextUtils.isEmpty(value)) {
-            config.preSharedKey = value;
-        } else {
-            config.preSharedKey = null;
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.Protocol.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.Protocol.strings);
-                if (0 <= index) {
-                    config.allowedProtocols.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.KeyMgmt.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
-                if (0 <= index) {
-                    config.allowedKeyManagement.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.AuthAlgorithm.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
-                if (0 <= index) {
-                    config.allowedAuthAlgorithms.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.PairwiseCipher.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
-                if (0 <= index) {
-                    config.allowedPairwiseCiphers.set(index);
-                }
-            }
-        }
-
-        value = mWifiStateTracker.getNetworkVariable(config.networkId,
-                WifiConfiguration.GroupCipher.varName);
-        if (!TextUtils.isEmpty(value)) {
-            String vals[] = value.split(" ");
-            for (String val : vals) {
-                int index =
-                    lookupString(val, WifiConfiguration.GroupCipher.strings);
-                if (0 <= index) {
-                    config.allowedGroupCiphers.set(index);
-                }
-            }
-        }
-
-        for (WifiConfiguration.EnterpriseField field :
-                config.enterpriseFields) {
-            value = mWifiStateTracker.getNetworkVariable(netId,
-                    field.varName());
-            if (!TextUtils.isEmpty(value)) {
-                if (field != config.eap) value = removeDoubleQuotes(value);
-                field.setValue(value);
-            }
-        }
-    }
-
-    private static String removeDoubleQuotes(String string) {
-        if (string.length() <= 2) return "";
-        return string.substring(1, string.length() - 1);
-    }
-
-    private static String convertToQuotedString(String string) {
-        return "\"" + string + "\"";
+        return mWifiStateTracker.getConfiguredNetworks();
     }
 
     /**
@@ -1018,280 +464,10 @@
      */
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
-
-        /*
-         * If the supplied networkId is -1, we create a new empty
-         * network configuration. Otherwise, the networkId should
-         * refer to an existing configuration.
-         */
-        int netId = config.networkId;
-        boolean newNetwork = netId == -1;
-        boolean doReconfig = false;
-        // networkId of -1 means we want to create a new network
-        synchronized (mWifiStateTracker) {
-            if (newNetwork) {
-                netId = mWifiStateTracker.addNetwork();
-                if (netId < 0) {
-                    if (DBG) {
-                        Slog.d(TAG, "Failed to add a network!");
-                    }
-                    return -1;
-                }
-                doReconfig = true;
-            }
-            mNeedReconfig = mNeedReconfig || doReconfig;
-        }
-
-        setVariables: {
-            /*
-             * Note that if a networkId for a non-existent network
-             * was supplied, then the first setNetworkVariable()
-             * will fail, so we don't bother to make a separate check
-             * for the validity of the ID up front.
-             */
-            if (config.SSID != null &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.ssidVarName,
-                        config.SSID)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set SSID: "+config.SSID);
-                }
-                break setVariables;
-            }
-
-            if (config.BSSID != null &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.bssidVarName,
-                        config.BSSID)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set BSSID: "+config.BSSID);
-                }
-                break setVariables;
-            }
-
-            String allowedKeyManagementString =
-                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
-            if (config.allowedKeyManagement.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.KeyMgmt.varName,
-                        allowedKeyManagementString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set key_mgmt: "+
-                            allowedKeyManagementString);
-                }
-                break setVariables;
-            }
-
-            String allowedProtocolsString =
-                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
-            if (config.allowedProtocols.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.Protocol.varName,
-                        allowedProtocolsString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set proto: "+
-                            allowedProtocolsString);
-                }
-                break setVariables;
-            }
-
-            String allowedAuthAlgorithmsString =
-                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
-            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.AuthAlgorithm.varName,
-                        allowedAuthAlgorithmsString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set auth_alg: "+
-                            allowedAuthAlgorithmsString);
-                }
-                break setVariables;
-            }
-
-            String allowedPairwiseCiphersString =
-                makeString(config.allowedPairwiseCiphers, WifiConfiguration.PairwiseCipher.strings);
-            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.PairwiseCipher.varName,
-                        allowedPairwiseCiphersString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set pairwise: "+
-                            allowedPairwiseCiphersString);
-                }
-                break setVariables;
-            }
-
-            String allowedGroupCiphersString =
-                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
-            if (config.allowedGroupCiphers.cardinality() != 0 &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.GroupCipher.varName,
-                        allowedGroupCiphersString)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set group: "+
-                            allowedGroupCiphersString);
-                }
-                break setVariables;
-            }
-
-            // Prevent client screw-up by passing in a WifiConfiguration we gave it
-            // by preventing "*" as a key.
-            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
-                    !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.pskVarName,
-                        config.preSharedKey)) {
-                if (DBG) {
-                    Slog.d(TAG, "failed to set psk: "+config.preSharedKey);
-                }
-                break setVariables;
-            }
-
-            boolean hasSetKey = false;
-            if (config.wepKeys != null) {
-                for (int i = 0; i < config.wepKeys.length; i++) {
-                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
-                    // by preventing "*" as a key.
-                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
-                        if (!mWifiStateTracker.setNetworkVariable(
-                                    netId,
-                                    WifiConfiguration.wepKeyVarNames[i],
-                                    config.wepKeys[i])) {
-                            if (DBG) {
-                                Slog.d(TAG,
-                                        "failed to set wep_key"+i+": " +
-                                        config.wepKeys[i]);
-                            }
-                            break setVariables;
-                        }
-                        hasSetKey = true;
-                    }
-                }
-            }
-
-            if (hasSetKey) {
-                if (!mWifiStateTracker.setNetworkVariable(
-                            netId,
-                            WifiConfiguration.wepTxKeyIdxVarName,
-                            Integer.toString(config.wepTxKeyIndex))) {
-                    if (DBG) {
-                        Slog.d(TAG,
-                                "failed to set wep_tx_keyidx: "+
-                                config.wepTxKeyIndex);
-                    }
-                    break setVariables;
-                }
-            }
-
-            if (!mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.priorityVarName,
-                        Integer.toString(config.priority))) {
-                if (DBG) {
-                    Slog.d(TAG, config.SSID + ": failed to set priority: "
-                            +config.priority);
-                }
-                break setVariables;
-            }
-
-            if (config.hiddenSSID && !mWifiStateTracker.setNetworkVariable(
-                        netId,
-                        WifiConfiguration.hiddenSSIDVarName,
-                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
-                if (DBG) {
-                    Slog.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
-                            config.hiddenSSID);
-                }
-                break setVariables;
-            }
-
-            for (WifiConfiguration.EnterpriseField field
-                    : config.enterpriseFields) {
-                String varName = field.varName();
-                String value = field.value();
-                if (value != null) {
-                    if (field != config.eap) {
-                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
-                    }
-                    if (!mWifiStateTracker.setNetworkVariable(
-                                netId,
-                                varName,
-                                value)) {
-                        if (DBG) {
-                            Slog.d(TAG, config.SSID + ": failed to set " + varName +
-                                    ": " + value);
-                        }
-                        break setVariables;
-                    }
-                }
-            }
-            return netId;
-        }
-
-        /*
-         * For an update, if one of the setNetworkVariable operations fails,
-         * we might want to roll back all the changes already made. But the
-         * chances are that if anything is going to go wrong, it'll happen
-         * the first time we try to set one of the variables.
-         */
-        if (newNetwork) {
-            removeNetwork(netId);
-            if (DBG) {
-                Slog.d(TAG,
-                        "Failed to set a network variable, removed network: "
-                        + netId);
-            }
-        }
-        return -1;
+        return mWifiStateTracker.addOrUpdateNetwork(config);
     }
 
-    private static String makeString(BitSet set, String[] strings) {
-        StringBuffer buf = new StringBuffer();
-        int nextSetBit = -1;
-
-        /* Make sure all set bits are in [0, strings.length) to avoid
-         * going out of bounds on strings.  (Shouldn't happen, but...) */
-        set = set.get(0, strings.length);
-
-        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
-            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
-        }
-
-        // remove trailing space
-        if (set.cardinality() > 0) {
-            buf.setLength(buf.length() - 1);
-        }
-
-        return buf.toString();
-    }
-
-    private static int lookupString(String string, String[] strings) {
-        int size = strings.length;
-
-        string = string.replace('-', '_');
-
-        for (int i = 0; i < size; i++)
-            if (string.equals(strings[i]))
-                return i;
-
-        if (DBG) {
-            // if we ever get here, we should probably add the
-            // value to WifiConfiguration to reflect that it's
-            // supported by the WPA supplicant
-            Slog.w(TAG, "Failed to look-up a string: " + string);
-        }
-
-        return -1;
-    }
-
-    /**
+     /**
      * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
      * @param netId the integer that identifies the network configuration
      * to the supplicant
@@ -1299,7 +475,6 @@
      */
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
-
         return mWifiStateTracker.removeNetwork(netId);
     }
 
@@ -1312,14 +487,7 @@
      */
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
-
-        String ifname = mWifiStateTracker.getInterfaceName();
-        NetworkUtils.enableInterface(ifname);
-        boolean result = mWifiStateTracker.enableNetwork(netId, disableOthers);
-        if (!result) {
-            NetworkUtils.disableInterface(ifname);
-        }
-        return result;
+        return mWifiStateTracker.enableNetwork(netId, disableOthers);
     }
 
     /**
@@ -1330,7 +498,6 @@
      */
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
-
         return mWifiStateTracker.disableNetwork(netId);
     }
 
@@ -1354,180 +521,19 @@
      */
     public List<ScanResult> getScanResults() {
         enforceAccessPermission();
-        String reply;
-
-        reply = mWifiStateTracker.scanResults();
-        if (reply == null) {
-            return null;
-        }
-
-        List<ScanResult> scanList = new ArrayList<ScanResult>();
-
-        int lineCount = 0;
-
-        int replyLen = reply.length();
-        // Parse the result string, keeping in mind that the last line does
-        // not end with a newline.
-        for (int lineBeg = 0, lineEnd = 0; lineEnd <= replyLen; ++lineEnd) {
-            if (lineEnd == replyLen || reply.charAt(lineEnd) == '\n') {
-                ++lineCount;
-                /*
-                 * Skip the first line, which is a header
-                 */
-                if (lineCount == 1) {
-                    lineBeg = lineEnd + 1;
-                    continue;
-                }
-                if (lineEnd > lineBeg) {
-                    String line = reply.substring(lineBeg, lineEnd);
-                    ScanResult scanResult = parseScanResult(line);
-                    if (scanResult != null) {
-                        scanList.add(scanResult);
-                    } else if (DBG) {
-                        Slog.w(TAG, "misformatted scan result for: " + line);
-                    }
-                }
-                lineBeg = lineEnd + 1;
-            }
-        }
-        mWifiStateTracker.setScanResultsList(scanList);
-        return scanList;
-    }
-
-    /**
-     * Parse the scan result line passed to us by wpa_supplicant (helper).
-     * @param line the line to parse
-     * @return the {@link ScanResult} object
-     */
-    private ScanResult parseScanResult(String line) {
-        ScanResult scanResult = null;
-        if (line != null) {
-            /*
-             * Cache implementation (LinkedHashMap) is not synchronized, thus,
-             * must synchronized here!
-             */
-            synchronized (mScanResultCache) {
-                String[] result = scanResultPattern.split(line);
-                if (3 <= result.length && result.length <= 5) {
-                    String bssid = result[0];
-                    // bssid | frequency | level | flags | ssid
-                    int frequency;
-                    int level;
-                    try {
-                        frequency = Integer.parseInt(result[1]);
-                        level = Integer.parseInt(result[2]);
-                        /* some implementations avoid negative values by adding 256
-                         * so we need to adjust for that here.
-                         */
-                        if (level > 0) level -= 256;
-                    } catch (NumberFormatException e) {
-                        frequency = 0;
-                        level = 0;
-                    }
-
-                    /*
-                     * The formatting of the results returned by
-                     * wpa_supplicant is intended to make the fields
-                     * line up nicely when printed,
-                     * not to make them easy to parse. So we have to
-                     * apply some heuristics to figure out which field
-                     * is the SSID and which field is the flags.
-                     */
-                    String ssid;
-                    String flags;
-                    if (result.length == 4) {
-                        if (result[3].charAt(0) == '[') {
-                            flags = result[3];
-                            ssid = "";
-                        } else {
-                            flags = "";
-                            ssid = result[3];
-                        }
-                    } else if (result.length == 5) {
-                        flags = result[3];
-                        ssid = result[4];
-                    } else {
-                        // Here, we must have 3 fields: no flags and ssid
-                        // set
-                        flags = "";
-                        ssid = "";
-                    }
-
-                    // bssid + ssid is the hash key
-                    String key = bssid + ssid;
-                    scanResult = mScanResultCache.get(key);
-                    if (scanResult != null) {
-                        scanResult.level = level;
-                        scanResult.SSID = ssid;
-                        scanResult.capabilities = flags;
-                        scanResult.frequency = frequency;
-                    } else {
-                        // Do not add scan results that have no SSID set
-                        if (0 < ssid.trim().length()) {
-                            scanResult =
-                                new ScanResult(
-                                    ssid, bssid, flags, level, frequency);
-                            mScanResultCache.put(key, scanResult);
-                        }
-                    }
-                } else {
-                    Slog.w(TAG, "Misformatted scan result text with " +
-                          result.length + " fields: " + line);
-                }
-            }
-        }
-
-        return scanResult;
-    }
-
-    /**
-     * Parse the "flags" field passed back in a scan result by wpa_supplicant,
-     * and construct a {@code WifiConfiguration} that describes the encryption,
-     * key management, and authenticaion capabilities of the access point.
-     * @param flags the string returned by wpa_supplicant
-     * @return the {@link WifiConfiguration} object, filled in
-     */
-    WifiConfiguration parseScanFlags(String flags) {
-        WifiConfiguration config = new WifiConfiguration();
-
-        if (flags.length() == 0) {
-            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        }
-        // ... to be implemented
-        return config;
+        return mWifiStateTracker.getScanResultsList();
     }
 
     /**
      * Tell the supplicant to persist the current list of configured networks.
      * @return {@code true} if the operation succeeded
+     *
+     * TODO: deprecate this
      */
     public boolean saveConfiguration() {
-        boolean result;
+        boolean result = true;
         enforceChangePermission();
-
-        synchronized (mWifiStateTracker) {
-            result = mWifiStateTracker.saveConfig();
-            if (result && mNeedReconfig) {
-                mNeedReconfig = false;
-                result = mWifiStateTracker.reloadConfig();
-
-                if (result) {
-                    Intent intent = new Intent(WifiManager.NETWORK_IDS_CHANGED_ACTION);
-                    mContext.sendBroadcast(intent);
-                }
-            }
-        }
-        // Inform the backup manager about a data change
-        IBackupManager ibm = IBackupManager.Stub.asInterface(
-                ServiceManager.getService(Context.BACKUP_SERVICE));
-        if (ibm != null) {
-            try {
-                ibm.dataChanged("com.android.providers.settings");
-            } catch (Exception e) {
-                // Try again later
-            }
-        }
-        return result;
+        return mWifiStateTracker.saveConfig();
     }
 
     /**
@@ -1542,7 +548,7 @@
      * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
      * {@code numChannels} is outside the valid range.
      */
-    public boolean setNumAllowedChannels(int numChannels, boolean persist) {
+    public synchronized boolean setNumAllowedChannels(int numChannels, boolean persist) {
         Slog.i(TAG, "WifiService trying to setNumAllowed to "+numChannels+
                 " with persist set to "+persist);
         enforceChangePermission();
@@ -1564,28 +570,15 @@
             return false;
         }
 
-        if (mWifiHandler == null) return false;
-
-        Message.obtain(mWifiHandler,
-                MESSAGE_SET_CHANNELS, numChannels, (persist ? 1 : 0)).sendToTarget();
-
-        return true;
-    }
-
-    /**
-     * sets the number of allowed radio frequency channels synchronously
-     * @param numChannels the number of allowed channels. Must be greater than 0
-     * and less than or equal to 16.
-     * @param persist {@code true} if the setting should be remembered.
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    private boolean setNumAllowedChannelsBlocking(int numChannels, boolean persist) {
         if (persist) {
             Settings.Secure.putInt(mContext.getContentResolver(),
                     Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
                     numChannels);
         }
-        return mWifiStateTracker.setNumAllowedChannels(numChannels);
+
+        mWifiStateTracker.setNumAllowedChannels(numChannels);
+
+        return true;
     }
 
     /**
@@ -1663,8 +656,8 @@
                     WifiInfo info = mWifiStateTracker.requestConnectionInfo();
                     if (info.getSupplicantState() != SupplicantState.COMPLETED) {
                         // we used to go to sleep immediately, but this caused some race conditions
-                        // we don't have time to track down for this release.  Delay instead, but not
-                        // as long as we would if connected (below)
+                        // we don't have time to track down for this release.  Delay instead,
+                        // but not as long as we would if connected (below)
                         // TODO - fix the race conditions and switch back to the immediate turn-off
                         long triggerTime = System.currentTimeMillis() + (2*60*1000); // 2 min
                         Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for 120,000 ms");
@@ -1763,31 +756,9 @@
         }
     };
 
-    private void sendEnableMessage(boolean enable, boolean persist, int uid) {
-        Message msg = Message.obtain(mWifiHandler,
-                                     (enable ? MESSAGE_ENABLE_WIFI : MESSAGE_DISABLE_WIFI),
-                                     (persist ? 1 : 0), uid);
-        msg.sendToTarget();
-    }
-
-    private void sendStartMessage(boolean scanOnlyMode) {
-        Message.obtain(mWifiHandler, MESSAGE_START_WIFI, scanOnlyMode ? 1 : 0, 0).sendToTarget();
-    }
-
-    private void sendAccessPointMessage(boolean enable, WifiConfiguration wifiConfig, int uid) {
-        Message.obtain(mWifiHandler,
-                (enable ? MESSAGE_START_ACCESS_POINT : MESSAGE_STOP_ACCESS_POINT),
-                uid, 0, wifiConfig).sendToTarget();
-    }
-
     private void updateWifiState() {
-        // send a message so it's all serialized
-        Message.obtain(mWifiHandler, MESSAGE_UPDATE_STATE, 0, 0).sendToTarget();
-    }
-
-    private void doUpdateWifiState() {
         boolean wifiEnabled = getPersistedWifiEnabled();
-        boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden;
+        boolean airplaneMode = isAirplaneModeOn() && !mAirplaneModeOverwridden.get();
         boolean lockHeld = mLocks.hasLocks();
         int strongestLockMode;
         boolean wifiShouldBeEnabled = wifiEnabled && !airplaneMode;
@@ -1798,36 +769,23 @@
             strongestLockMode = WifiManager.WIFI_MODE_FULL;
         }
 
-        synchronized (mWifiHandler) {
-            if ((mWifiStateTracker.getWifiState() == WIFI_STATE_ENABLING) && !airplaneMode) {
-                return;
-            }
+        /* Disable tethering when airplane mode is enabled */
+        if (airplaneMode) {
+            mWifiStateTracker.setWifiApEnabled(null, false);
+        }
 
-            /* Disable tethering when airplane mode is enabled */
-            if (airplaneMode &&
-                (mWifiApState == WIFI_AP_STATE_ENABLING || mWifiApState == WIFI_AP_STATE_ENABLED)) {
-                sWakeLock.acquire();
-                sendAccessPointMessage(false, null, mLastApEnableUid);
-            }
-
-            if (wifiShouldBeEnabled) {
-                if (wifiShouldBeStarted) {
-                    sWakeLock.acquire();
-                    sendEnableMessage(true, false, mLastEnableUid);
-                    sWakeLock.acquire();
-                    sendStartMessage(strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
-                } else if (!mWifiStateTracker.isDriverStopped()) {
-                    if (mCm == null) {
-                        mCm = (ConnectivityManager)mContext.
-                                getSystemService(Context.CONNECTIVITY_SERVICE);
-                    }
-                    mCm.requestNetworkTransitionWakelock(TAG);
-                    mWifiHandler.sendEmptyMessage(MESSAGE_STOP_WIFI);
-                }
+        if (wifiShouldBeEnabled) {
+            if (wifiShouldBeStarted) {
+                mWifiStateTracker.setWifiEnabled(true);
+                mWifiStateTracker.setScanOnlyMode(
+                        strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
+                mWifiStateTracker.startWifi(true);
             } else {
-                sWakeLock.acquire();
-                sendEnableMessage(false, false, mLastEnableUid);
+                mWifiStateTracker.requestCmWakeLock();
+                mWifiStateTracker.disconnectAndStop();
             }
+        } else {
+            mWifiStateTracker.setWifiEnabled(false);
         }
     }
 
@@ -1865,71 +823,6 @@
                 Settings.System.AIRPLANE_MODE_ON, 0) == 1;
     }
 
-    /**
-     * Handler that allows posting to the WifiThread.
-     */
-    private class WifiHandler extends Handler {
-        public WifiHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-
-                case MESSAGE_ENABLE_WIFI:
-                    setWifiEnabledBlocking(true, msg.arg1 == 1, msg.arg2);
-                    if (mWifiWatchdogService == null) {
-                        mWifiWatchdogService = new WifiWatchdogService(mContext, mWifiStateTracker);
-                    }
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_START_WIFI:
-                    mWifiStateTracker.setScanOnlyMode(msg.arg1 != 0);
-                    mWifiStateTracker.restart();
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_UPDATE_STATE:
-                    doUpdateWifiState();
-                    break;
-
-                case MESSAGE_DISABLE_WIFI:
-                    // a non-zero msg.arg1 value means the "enabled" setting
-                    // should be persisted
-                    setWifiEnabledBlocking(false, msg.arg1 == 1, msg.arg2);
-                    mWifiWatchdogService = null;
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_STOP_WIFI:
-                    mWifiStateTracker.disconnectAndStop();
-                    // don't release wakelock
-                    break;
-
-                case MESSAGE_START_ACCESS_POINT:
-                    setWifiApEnabledBlocking(true,
-                                             msg.arg1,
-                                             (WifiConfiguration) msg.obj);
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_STOP_ACCESS_POINT:
-                    setWifiApEnabledBlocking(false,
-                                             msg.arg1,
-                                             (WifiConfiguration) msg.obj);
-                    sWakeLock.release();
-                    break;
-
-                case MESSAGE_SET_CHANNELS:
-                    setNumAllowedChannelsBlocking(msg.arg1, msg.arg2 == 1);
-                    break;
-
-            }
-        }
-    }
-
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
@@ -1939,7 +832,7 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        pw.println("Wi-Fi is " + stateName(mWifiStateTracker.getWifiState()));
+        pw.println("Wi-Fi is " + mWifiStateTracker.getWifiStateByName());
         pw.println("Stay-awake conditions: " +
                 Settings.System.getInt(mContext.getContentResolver(),
                                        Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0));
@@ -1971,23 +864,6 @@
         mLocks.dump(pw);
     }
 
-    private static String stateName(int wifiState) {
-        switch (wifiState) {
-            case WIFI_STATE_DISABLING:
-                return "disabling";
-            case WIFI_STATE_DISABLED:
-                return "disabled";
-            case WIFI_STATE_ENABLING:
-                return "enabling";
-            case WIFI_STATE_ENABLED:
-                return "enabled";
-            case WIFI_STATE_UNKNOWN:
-                return "unknown state";
-            default:
-                return "[invalid state]";
-        }
-    }
-
     private class WifiLock extends DeathRecipient {
         WifiLock(int lockMode, String tag, IBinder binder) {
             super(lockMode, tag, binder);
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 445dd03..be14cd3 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -275,12 +275,13 @@
     /**
      * Unregister broadcasts and quit the watchdog thread
      */
-    private void quit() {
-        unregisterForWifiBroadcasts();
-        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
-        mHandler.removeAllActions();
-        mHandler.getLooper().quit();
-    }
+    //TODO: Change back to running WWS when needed
+//    private void quit() {
+//        unregisterForWifiBroadcasts();
+//        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+//        mHandler.removeAllActions();
+//        mHandler.getLooper().quit();
+//    }
 
     /**
      * Waits for the main watchdog thread to create the handler.
@@ -751,7 +752,7 @@
         // Black list this "bad" AP, this will cause an attempt to connect to another
         blacklistAp(ap.bssid);
         // Initiate an association to an alternate AP
-        mWifiStateTracker.reassociate();
+        mWifiStateTracker.reassociateCommand();
     }
 
     private void blacklistAp(String bssid) {
@@ -762,10 +763,7 @@
         // Before taking action, make sure we should not cancel our processing
         if (shouldCancel()) return;
         
-        if (!mWifiStateTracker.addToBlacklist(bssid)) {
-            // There's a known bug where this method returns failure on success
-            //Slog.e(TAG, "Blacklisting " + bssid + " failed");
-        }
+        mWifiStateTracker.addToBlacklist(bssid);
 
         if (D) {
             myLogD("Blacklisting " + bssid);
@@ -860,10 +858,7 @@
              * (and blacklisted them). Clear the blacklist so the AP with best
              * signal is chosen.
              */
-            if (!mWifiStateTracker.clearBlacklist()) {
-                // There's a known bug where this method returns failure on success
-                //Slog.e(TAG, "Clearing blacklist failed");
-            }
+            mWifiStateTracker.clearBlacklist();
             
             if (V) {
                 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
@@ -1151,7 +1146,7 @@
 
         private void handleWifiStateChanged(int wifiState) {
             if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
-                quit();
+                onDisconnected();
             } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
                 onEnabled();
             }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index b82ec01..1a8efa1 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -4306,7 +4306,7 @@
                 "getSwitchState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getSwitchState(sw);
+        return mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, sw);
     }
 
     public int getSwitchStateForDevice(int devid, int sw) {
@@ -4314,7 +4314,7 @@
                 "getSwitchStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getSwitchState(devid, sw);
+        return mInputManager.getSwitchState(devid, InputDevice.SOURCE_ANY, sw);
     }
 
     public int getScancodeState(int sw) {
@@ -4322,7 +4322,7 @@
                 "getScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getScancodeState(sw);
+        return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_ANY, sw);
     }
 
     public int getScancodeStateForDevice(int devid, int sw) {
@@ -4330,7 +4330,7 @@
                 "getScancodeStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getScancodeState(devid, sw);
+        return mInputManager.getScanCodeState(devid, InputDevice.SOURCE_ANY, sw);
     }
 
     public int getTrackballScancodeState(int sw) {
@@ -4338,7 +4338,7 @@
                 "getTrackballScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getTrackballScancodeState(sw);
+        return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw);
     }
 
     public int getDPadScancodeState(int sw) {
@@ -4346,7 +4346,7 @@
                 "getDPadScancodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getDPadScancodeState(sw);
+        return mInputManager.getScanCodeState(-1, InputDevice.SOURCE_DPAD, sw);
     }
 
     public int getKeycodeState(int sw) {
@@ -4354,7 +4354,7 @@
                 "getKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getKeycodeState(sw);
+        return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, sw);
     }
 
     public int getKeycodeStateForDevice(int devid, int sw) {
@@ -4362,7 +4362,7 @@
                 "getKeycodeStateForDevice()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getKeycodeState(devid, sw);
+        return mInputManager.getKeyCodeState(devid, InputDevice.SOURCE_ANY, sw);
     }
 
     public int getTrackballKeycodeState(int sw) {
@@ -4370,7 +4370,7 @@
                 "getTrackballKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getTrackballKeycodeState(sw);
+        return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_TRACKBALL, sw);
     }
 
     public int getDPadKeycodeState(int sw) {
@@ -4378,11 +4378,11 @@
                 "getDPadKeycodeState()")) {
             throw new SecurityException("Requires READ_INPUT_STATE permission");
         }
-        return mInputManager.getDPadKeycodeState(sw);
+        return mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD, sw);
     }
 
     public boolean hasKeys(int[] keycodes, boolean[] keyExists) {
-        return mInputManager.hasKeys(keycodes, keyExists);
+        return mInputManager.hasKeys(-1, InputDevice.SOURCE_ANY, keycodes, keyExists);
     }
 
     public void enableScreenAfterBoot() {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index fa2ec1f..440ebbd 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6173,7 +6173,7 @@
         if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
 
         boolean bufferWasEmpty;
-
+        boolean needsFlush;
         final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024);
         synchronized (sb) {
             bufferWasEmpty = sb.length() == 0;
@@ -6188,18 +6188,32 @@
                 sb.append(crashInfo.stackTrace);
             }
             sb.append("\n");
+
+            // Only buffer up to ~64k.  Various logging bits truncate
+            // things at 128k.
+            needsFlush = (sb.length() > 64 * 1024);
         }
 
-        // Non-system apps are isolated with a different tag & policy.
-        // They're also not batched.  Batching is useful during system
-        // boot with strict system-wide logging policies and lots of
-        // things firing, but not common with regular apps, which
-        // won't ship with StrictMode dropboxing enabled.
-        if (!isSystemApp) {
+        // Flush immediately if the buffer's grown too large, or this
+        // is a non-system app.  Non-system apps are isolated with a
+        // different tag & policy and not batched.
+        //
+        // Batching is useful during internal testing with
+        // StrictMode settings turned up high.  Without batching,
+        // thousands of separate files could be created on boot.
+        if (!isSystemApp || needsFlush) {
             new Thread("Error dump: " + dropboxTag) {
                 @Override
                 public void run() {
-                    dbox.addText(dropboxTag, sb.toString());
+                    String report;
+                    synchronized (sb) {
+                        report = sb.toString();
+                        sb.delete(0, sb.length());
+                        sb.trimToSize();
+                    }
+                    if (report.length() != 0) {
+                        dbox.addText(dropboxTag, report);
+                    }
                 }
             }.start();
             return;
@@ -6207,8 +6221,9 @@
 
         // System app batching:
         if (!bufferWasEmpty) {
-            // An existing dropbox-writing thread is outstanding and
-            // will handle it.
+            // An existing dropbox-writing thread is outstanding, so
+            // we don't need to start it up.  The existing thread will
+            // catch the buffer appends we just did.
             return;
         }
 
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index d27ccc0..acedd26 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -26,13 +26,13 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.hardware.Usb;
 import android.net.ConnectivityManager;
 import android.net.InterfaceConfiguration;
 import android.net.IConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.NetworkInfo;
 import android.net.NetworkUtils;
-import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.HandlerThread;
@@ -136,7 +136,7 @@
 
         mStateReceiver = new StateReceiver();
         IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        filter.addAction(Usb.ACTION_USB_STATE);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
         filter.addAction(Intent.ACTION_BOOT_COMPLETED);
         mContext.registerReceiver(mStateReceiver, filter);
@@ -429,10 +429,9 @@
     private class StateReceiver extends BroadcastReceiver {
         public void onReceive(Context content, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
-                mUsbConnected = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1)
-                        == BatteryManager.BATTERY_PLUGGED_USB);
-                Tethering.this.updateUsbStatus();
+            if (action.equals(Usb.ACTION_USB_STATE)) {
+                mUsbConnected = intent.getExtras().getBoolean(Usb.USB_CONNECTED);
+                updateUsbStatus();
             } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
                 mUsbMassStorageOff = false;
                 updateUsbStatus();
diff --git a/services/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/java/com/android/server/location/ComprehensiveCountryDetector.java
new file mode 100755
index 0000000..e692f8d
--- /dev/null
+++ b/services/java/com/android/server/location/ComprehensiveCountryDetector.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Geocoder;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+
+/**
+ * This class is used to detect the country where the user is. The sources of
+ * country are queried in order of reliability, like
+ * <ul>
+ * <li>Mobile network</li>
+ * <li>Location</li>
+ * <li>SIM's country</li>
+ * <li>Phone's locale</li>
+ * </ul>
+ * <p>
+ * Call the {@link #detectCountry()} to get the available country immediately.
+ * <p>
+ * To be notified of the future country change, using the
+ * {@link #setCountryListener(CountryListener)}
+ * <p>
+ * Using the {@link #stop()} to stop listening to the country change.
+ * <p>
+ * The country information will be refreshed every
+ * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used.
+ *
+ * @hide
+ */
+public class ComprehensiveCountryDetector extends CountryDetectorBase {
+
+    private final static String TAG = "ComprehensiveCountryDetector";
+
+    /**
+     * The refresh interval when the location based country was used
+     */
+    private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
+
+    protected CountryDetectorBase mLocationBasedCountryDetector;
+    protected Timer mLocationRefreshTimer;
+
+    private final int mPhoneType;
+    private Country mCountry;
+    private TelephonyManager mTelephonyManager;
+    private Country mCountryFromLocation;
+    private boolean mStopped = false;
+    private ServiceState mLastState;
+
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onServiceStateChanged(ServiceState serviceState) {
+            // TODO: Find out how often we will be notified, if this method is called too
+            // many times, let's consider querying the network.
+            Slog.d(TAG, "onServiceStateChanged");
+            // We only care the state change
+            if (mLastState == null || mLastState.getState() != serviceState.getState()) {
+                detectCountry(true, true);
+                mLastState = new ServiceState(serviceState);
+            }
+        }
+    };
+
+    /**
+     * The listener for receiving the notification from LocationBasedCountryDetector.
+     */
+    private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
+        public void onCountryDetected(Country country) {
+            mCountryFromLocation = country;
+            // Don't start the LocationBasedCountryDetector.
+            detectCountry(true, false);
+            stopLocationBasedDetector();
+        }
+    };
+
+    public ComprehensiveCountryDetector(Context context) {
+        super(context);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mPhoneType = mTelephonyManager.getPhoneType();
+    }
+
+    @Override
+    public Country detectCountry() {
+        // Don't start the LocationBasedCountryDetector if we have been stopped.
+        return detectCountry(false, !mStopped);
+    }
+
+    @Override
+    public void stop() {
+        Slog.i(TAG, "Stop the detector.");
+        cancelLocationRefresh();
+        removePhoneStateListener();
+        stopLocationBasedDetector();
+        mListener = null;
+        mStopped = true;
+    }
+
+    /**
+     * Get the country from different sources in order of the reliability.
+     */
+    private Country getCountry() {
+        Country result = null;
+        result = getNetworkBasedCountry();
+        if (result == null) {
+            result = getLastKnownLocationBasedCountry();
+        }
+        if (result == null) {
+            result = getSimBasedCountry();
+        }
+        if (result == null) {
+            result = getLocaleCountry();
+        }
+        return result;
+    }
+
+    /**
+     * @return the country from the mobile network.
+     */
+    protected Country getNetworkBasedCountry() {
+        String countryIso = null;
+        // TODO: The document says the result may be unreliable on CDMA networks. Shall we use
+        // it on CDMA phone? We may test the Android primarily used countries.
+        if (mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+            countryIso = mTelephonyManager.getNetworkCountryIso();
+            if (!TextUtils.isEmpty(countryIso)) {
+                return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the cached location based country.
+     */
+    protected Country getLastKnownLocationBasedCountry() {
+        return mCountryFromLocation;
+    }
+
+    /**
+     * @return the country from SIM card
+     */
+    protected Country getSimBasedCountry() {
+        String countryIso = null;
+        countryIso = mTelephonyManager.getSimCountryIso();
+        if (!TextUtils.isEmpty(countryIso)) {
+            return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
+        }
+        return null;
+    }
+
+    /**
+     * @return the country from the system's locale.
+     */
+    protected Country getLocaleCountry() {
+        Locale defaultLocale = Locale.getDefault();
+        if (defaultLocale != null) {
+            return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @param notifyChange indicates whether the listener should be notified the change of the
+     * country
+     * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
+     * be started if the current country source is less reliable than the location.
+     * @return the current available UserCountry
+     */
+    private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
+        Country country = getCountry();
+        runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
+                notifyChange, startLocationBasedDetection);
+        mCountry = country;
+        return mCountry;
+    }
+
+    /**
+     * Run the tasks in the service's thread.
+     */
+    protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+            final boolean notifyChange, final boolean startLocationBasedDetection) {
+        mHandler.post(new Runnable() {
+            public void run() {
+                runAfterDetection(
+                        country, detectedCountry, notifyChange, startLocationBasedDetection);
+            }
+        });
+    }
+
+    @Override
+    public void setCountryListener(CountryListener listener) {
+        CountryListener prevListener = mListener;
+        mListener = listener;
+        if (mListener == null) {
+            // Stop listening all services
+            removePhoneStateListener();
+            stopLocationBasedDetector();
+            cancelLocationRefresh();
+        } else if (prevListener == null) {
+            addPhoneStateListener();
+            detectCountry(false, true);
+        }
+    }
+
+    void runAfterDetection(final Country country, final Country detectedCountry,
+            final boolean notifyChange, final boolean startLocationBasedDetection) {
+        if (notifyChange) {
+            notifyIfCountryChanged(country, detectedCountry);
+        }
+        if (startLocationBasedDetection && (detectedCountry == null
+                || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
+                && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
+            // Start finding location when the source is less reliable than the
+            // location and the airplane mode is off (as geocoder will not
+            // work).
+            // TODO : Shall we give up starting the detector within a
+            // period of time?
+            startLocationBasedDetector(mLocationBasedCountryDetectionListener);
+        }
+        if (detectedCountry == null
+                || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
+            // Schedule the location refresh if the country source is
+            // not more reliable than the location or no country is
+            // found.
+            // TODO: Listen to the preference change of GPS, Wifi etc,
+            // and start detecting the country.
+            scheduleLocationRefresh();
+        } else {
+            // Cancel the location refresh once the current source is
+            // more reliable than the location.
+            cancelLocationRefresh();
+            stopLocationBasedDetector();
+        }
+    }
+
+    /**
+     * Find the country from LocationProvider.
+     */
+    private synchronized void startLocationBasedDetector(CountryListener listener) {
+        if (mLocationBasedCountryDetector != null) {
+            return;
+        }
+        mLocationBasedCountryDetector = createLocationBasedCountryDetector();
+        mLocationBasedCountryDetector.setCountryListener(listener);
+        mLocationBasedCountryDetector.detectCountry();
+    }
+
+    private synchronized void stopLocationBasedDetector() {
+        if (mLocationBasedCountryDetector != null) {
+            mLocationBasedCountryDetector.stop();
+            mLocationBasedCountryDetector = null;
+        }
+    }
+
+    protected CountryDetectorBase createLocationBasedCountryDetector() {
+        return new LocationBasedCountryDetector(mContext);
+    }
+
+    protected boolean isAirplaneModeOff() {
+        return Settings.System.getInt(
+                mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) == 0;
+    }
+
+    /**
+     * Notify the country change.
+     */
+    private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
+        if (detectedCountry != null && mListener != null
+                && (country == null || !country.equals(detectedCountry))) {
+            Slog.d(TAG,
+                    "The country was changed from " + country != null ? country.getCountryIso() :
+                        country + " to " + detectedCountry.getCountryIso());
+            notifyListener(detectedCountry);
+        }
+    }
+
+    /**
+     * Schedule the next location refresh. We will do nothing if the scheduled task exists.
+     */
+    private synchronized void scheduleLocationRefresh() {
+        if (mLocationRefreshTimer != null) return;
+        mLocationRefreshTimer = new Timer();
+        mLocationRefreshTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                mLocationRefreshTimer = null;
+                detectCountry(false, true);
+            }
+        }, LOCATION_REFRESH_INTERVAL);
+    }
+
+    /**
+     * Cancel the scheduled refresh task if it exists
+     */
+    private synchronized void cancelLocationRefresh() {
+        if (mLocationRefreshTimer != null) {
+            mLocationRefreshTimer.cancel();
+            mLocationRefreshTimer = null;
+        }
+    }
+
+    protected synchronized void addPhoneStateListener() {
+        if (mPhoneStateListener == null && mPhoneType == TelephonyManager.PHONE_TYPE_GSM) {
+            mLastState = null;
+            mPhoneStateListener = new PhoneStateListener() {
+                @Override
+                public void onServiceStateChanged(ServiceState serviceState) {
+                    // TODO: Find out how often we will be notified, if this
+                    // method is called too
+                    // many times, let's consider querying the network.
+                    Slog.d(TAG, "onServiceStateChanged");
+                    // We only care the state change
+                    if (mLastState == null || mLastState.getState() != serviceState.getState()) {
+                        detectCountry(true, true);
+                        mLastState = new ServiceState(serviceState);
+                    }
+                }
+            };
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        }
+    }
+
+    protected synchronized void removePhoneStateListener() {
+        if (mPhoneStateListener != null) {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+            mPhoneStateListener = null;
+        }
+    }
+
+    protected boolean isGeoCoderImplemented() {
+        return Geocoder.isImplemented();
+    }
+}
diff --git a/services/java/com/android/server/location/CountryDetectorBase.java b/services/java/com/android/server/location/CountryDetectorBase.java
new file mode 100644
index 0000000..8326ef9
--- /dev/null
+++ b/services/java/com/android/server/location/CountryDetectorBase.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.os.Handler;
+
+/**
+ * This class defines the methods need to be implemented by the country
+ * detector.
+ * <p>
+ * Calling {@link #detectCountry} to start detecting the country. The country
+ * could be returned immediately if it is available.
+ *
+ * @hide
+ */
+public abstract class CountryDetectorBase {
+    protected final Handler mHandler;
+    protected final Context mContext;
+    protected CountryListener mListener;
+    protected Country mDetectedCountry;
+
+    public CountryDetectorBase(Context ctx) {
+        mContext = ctx;
+        mHandler = new Handler();
+    }
+
+    /**
+     * Start detecting the country that the user is in.
+     *
+     * @return the country if it is available immediately, otherwise null should
+     *         be returned.
+     */
+    public abstract Country detectCountry();
+
+    /**
+     * Register a listener to receive the notification when the country is detected or changed.
+     * <p>
+     * The previous listener will be replaced if it exists.
+     */
+    public void setCountryListener(CountryListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Stop detecting the country. The detector should release all system services and be ready to
+     * be freed
+     */
+    public abstract void stop();
+
+    protected void notifyListener(Country country) {
+        if (mListener != null) {
+            mListener.onCountryDetected(country);
+        }
+    }
+}
diff --git a/services/java/com/android/server/location/LocationBasedCountryDetector.java b/services/java/com/android/server/location/LocationBasedCountryDetector.java
new file mode 100755
index 0000000..139f05d
--- /dev/null
+++ b/services/java/com/android/server/location/LocationBasedCountryDetector.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.Country;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Slog;
+
+/**
+ * This class detects which country the user currently is in through the enabled
+ * location providers and the GeoCoder
+ * <p>
+ * Use {@link #detectCountry} to start querying. If the location can not be
+ * resolved within the given time, the last known location will be used to get
+ * the user country through the GeoCoder. The IllegalStateException will be
+ * thrown if there is a ongoing query.
+ * <p>
+ * The current query can be stopped by {@link #stop()}
+ *
+ * @hide
+ */
+public class LocationBasedCountryDetector extends CountryDetectorBase {
+    private final static String TAG = "LocationBasedCountryDetector";
+    private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins
+
+    /**
+     * Used for canceling location query
+     */
+    protected Timer mTimer;
+
+    /**
+     * The thread to query the country from the GeoCoder.
+     */
+    protected Thread mQueryThread;
+    protected List<LocationListener> mLocationListeners;
+
+    private LocationManager mLocationManager;
+    private List<String> mEnabledProviders;
+
+    public LocationBasedCountryDetector(Context ctx) {
+        super(ctx);
+        mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
+    }
+
+    /**
+     * @return the ISO 3166-1 two letters country code from the location
+     */
+    protected String getCountryFromLocation(Location location) {
+        String country = null;
+        Geocoder geoCoder = new Geocoder(mContext);
+        try {
+            List<Address> addresses = geoCoder.getFromLocation(
+                    location.getLatitude(), location.getLongitude(), 1);
+            if (addresses != null && addresses.size() > 0) {
+                country = addresses.get(0).getCountryCode();
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Exception occurs when getting country from location");
+        }
+        return country;
+    }
+
+    /**
+     * Register the listeners with the location providers
+     */
+    protected void registerEnabledProviders(List<LocationListener> listeners) {
+        int total = listeners.size();
+        for (int i = 0; i< total; i++) {
+            mLocationManager.requestLocationUpdates(
+                    mEnabledProviders.get(i), 0, 0, listeners.get(i));
+        }
+    }
+
+    /**
+     * Unregister the listeners with the location providers
+     */
+    protected void unregisterProviders(List<LocationListener> listeners) {
+        for (LocationListener listener : listeners) {
+            mLocationManager.removeUpdates(listener);
+        }
+    }
+
+    /**
+     * @return the last known location from all providers
+     */
+    protected Location getLastKnownLocation() {
+        List<String> providers = mLocationManager.getAllProviders();
+        Location bestLocation = null;
+        for (String provider : providers) {
+            Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
+            if (lastKnownLocation != null) {
+                if (bestLocation == null || bestLocation.getTime() < lastKnownLocation.getTime()) {
+                    bestLocation = lastKnownLocation;
+                }
+            }
+        }
+        return bestLocation;
+    }
+
+    /**
+     * @return the timeout for querying the location.
+     */
+    protected long getQueryLocationTimeout() {
+        return QUERY_LOCATION_TIMEOUT;
+    }
+
+    /**
+     * @return the total number of enabled location providers
+     */
+    protected int getTotalEnabledProviders() {
+        if (mEnabledProviders == null) {
+            mEnabledProviders = mLocationManager.getProviders(true);
+        }
+        return mEnabledProviders.size();
+    }
+
+    /**
+     * Start detecting the country.
+     * <p>
+     * Queries the location from all location providers, then starts a thread to query the
+     * country from GeoCoder.
+     */
+    @Override
+    public synchronized Country detectCountry() {
+        if (mLocationListeners  != null) {
+            throw new IllegalStateException();
+        }
+        // Request the location from all enabled providers.
+        int totalProviders = getTotalEnabledProviders();
+        if (totalProviders > 0) {
+            mLocationListeners = new ArrayList<LocationListener>(totalProviders);
+            for (int i = 0; i < totalProviders; i++) {
+                LocationListener listener = new LocationListener () {
+                    public void onLocationChanged(Location location) {
+                        if (location != null) {
+                            LocationBasedCountryDetector.this.stop();
+                            queryCountryCode(location);
+                        }
+                    }
+                    public void onProviderDisabled(String provider) {
+                    }
+                    public void onProviderEnabled(String provider) {
+                    }
+                    public void onStatusChanged(String provider, int status, Bundle extras) {
+                    }
+                };
+                mLocationListeners.add(listener);
+            }
+            registerEnabledProviders(mLocationListeners);
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    mTimer = null;
+                    LocationBasedCountryDetector.this.stop();
+                    // Looks like no provider could provide the location, let's try the last
+                    // known location.
+                    queryCountryCode(getLastKnownLocation());
+                }
+            }, getQueryLocationTimeout());
+        } else {
+            // There is no provider enabled.
+            queryCountryCode(getLastKnownLocation());
+        }
+        return mDetectedCountry;
+    }
+
+    /**
+     * Stop the current query without notifying the listener.
+     */
+    @Override
+    public synchronized void stop() {
+        if (mLocationListeners != null) {
+            unregisterProviders(mLocationListeners);
+            mLocationListeners = null;
+        }
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer = null;
+        }
+    }
+
+    /**
+     * Start a new thread to query the country from Geocoder.
+     */
+    private synchronized void queryCountryCode(final Location location) {
+        if (location == null) {
+            notifyListener(null);
+            return;
+        }
+        if (mQueryThread != null) return;
+        mQueryThread = new Thread(new Runnable() {
+            public void run() {
+                String countryIso = null;
+                if (location != null) {
+                    countryIso = getCountryFromLocation(location);
+                }
+                if (countryIso != null) {
+                    mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
+                } else {
+                    mDetectedCountry = null;
+                }
+                notifyListener(mDetectedCountry);
+                mQueryThread = null;
+            }
+        });
+        mQueryThread.start();
+    }
+}
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 0992b33..a332376 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -219,11 +219,10 @@
             int32_t* width, int32_t* height, int32_t* orientation);
     virtual void virtualKeyDownFeedback();
     virtual int32_t interceptKey(nsecs_t when, int32_t deviceId,
-            bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags);
-    virtual int32_t interceptTrackball(nsecs_t when, bool buttonChanged, bool buttonDown,
-            bool rolled);
-    virtual int32_t interceptTouch(nsecs_t when);
-    virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue);
+            bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags);
+    virtual int32_t interceptSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue,
+            uint32_t& policyFlags);
+    virtual int32_t interceptGeneric(nsecs_t when, uint32_t& policyFlags);
     virtual bool filterTouchEvents();
     virtual bool filterJumpyTouchEvents();
     virtual void getVirtualKeyDefinitions(const String8& deviceName,
@@ -343,6 +342,7 @@
     InputApplication* mFocusedApplication;
     InputApplication mFocusedApplicationStorage; // preallocated storage for mFocusedApplication
 
+    void dumpDeviceInfo(String8& dump);
     void dumpDispatchStateLd(String8& dump);
     void logDispatchStateLd();
 
@@ -409,12 +409,16 @@
 
 String8 NativeInputManager::dump() {
     String8 dump;
-    dump.append("Native Input Dispatcher State:\n");
-
     { // acquire lock
         AutoMutex _l(mDisplayLock);
+        dump.append("Native Input Dispatcher State:\n");
         dumpDispatchStateLd(dump);
+        dump.append("\n");
     } // release lock
+
+    dump.append("Input Devices:\n");
+    dumpDeviceInfo(dump);
+
     return dump;
 }
 
@@ -566,9 +570,15 @@
         AutoMutex _l(mDisplayLock);
 
         if (mDisplayWidth > 0) {
-            *width = mDisplayWidth;
-            *height = mDisplayHeight;
-            *orientation = mDisplayOrientation;
+            if (width) {
+                *width = mDisplayWidth;
+            }
+            if (height) {
+                *height = mDisplayHeight;
+            }
+            if (orientation) {
+                *orientation = mDisplayOrientation;
+            }
             result = true;
         }
     }
@@ -595,7 +605,7 @@
 }
 
 int32_t NativeInputManager::interceptKey(nsecs_t when,
-        int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
+        int32_t deviceId, bool down, int32_t keyCode, int32_t scanCode, uint32_t& policyFlags) {
 #if DEBUG_INPUT_READER_POLICY
     LOGD("interceptKey - when=%lld, deviceId=%d, down=%d, keyCode=%d, scanCode=%d, "
             "policyFlags=0x%x",
@@ -626,12 +636,12 @@
     int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
     if (! isScreenOn) {
         // Key presses and releases wake the device.
-        actions |= InputReaderPolicyInterface::ACTION_WOKE_HERE;
+        policyFlags |= POLICY_FLAG_WOKE_HERE;
     }
 
     if (! isScreenBright) {
         // Key presses and releases brighten the screen if dimmed.
-        actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE;
+        policyFlags |= POLICY_FLAG_BRIGHT_HERE;
     }
 
     if (wmActions & WM_ACTION_GO_TO_SLEEP) {
@@ -658,42 +668,20 @@
     return actions;
 }
 
-int32_t NativeInputManager::interceptTouch(nsecs_t when) {
+int32_t NativeInputManager::interceptGeneric(nsecs_t when, uint32_t& policyFlags) {
 #if DEBUG_INPUT_READER_POLICY
-    LOGD("interceptTouch - when=%lld", when);
+    LOGD("interceptGeneric - when=%lld, policyFlags=0x%x", when, policyFlags);
 #endif
 
     int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
     if (isScreenOn()) {
-        // Only dispatch touch events when the device is awake.
+        // Only dispatch events when the device is awake.
         // Do not wake the device.
         actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
 
         if (! isScreenBright()) {
             // Brighten the screen if dimmed.
-            actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE;
-        }
-    }
-
-    return actions;
-}
-
-int32_t NativeInputManager::interceptTrackball(nsecs_t when,
-        bool buttonChanged, bool buttonDown, bool rolled) {
-#if DEBUG_INPUT_READER_POLICY
-    LOGD("interceptTrackball - when=%lld, buttonChanged=%d, buttonDown=%d, rolled=%d",
-            when, buttonChanged, buttonDown, rolled);
-#endif
-
-    int32_t actions = InputReaderPolicyInterface::ACTION_NONE;
-    if (isScreenOn()) {
-        // Only dispatch trackball events when the device is awake.
-        // Do not wake the device.
-        actions |= InputReaderPolicyInterface::ACTION_DISPATCH;
-
-        if (! isScreenBright()) {
-            // Brighten the screen if dimmed.
-            actions |= InputReaderPolicyInterface::ACTION_BRIGHT_HERE;
+            policyFlags |= POLICY_FLAG_BRIGHT_HERE;
         }
     }
 
@@ -701,10 +689,10 @@
 }
 
 int32_t NativeInputManager::interceptSwitch(nsecs_t when, int32_t switchCode,
-        int32_t switchValue) {
+        int32_t switchValue, uint32_t& policyFlags) {
 #if DEBUG_INPUT_READER_POLICY
-    LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d",
-            when, switchCode, switchValue);
+    LOGD("interceptSwitch - when=%lld, switchCode=%d, switchValue=%d, policyFlags=0x%x",
+            when, switchCode, switchValue, policyFlags);
 #endif
 
     JNIEnv* env = jniEnv();
@@ -1718,6 +1706,56 @@
     android_server_PowerManagerService_userActivity(eventTime, eventType);
 }
 
+static void dumpMotionRange(String8& dump,
+        const char* name, const InputDeviceInfo::MotionRange* range) {
+    if (range) {
+        dump.appendFormat("      %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
+                name, range->min, range->max, range->flat, range->fuzz);
+    }
+}
+
+#define DUMP_MOTION_RANGE(range) \
+    dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));
+
+void NativeInputManager::dumpDeviceInfo(String8& dump) {
+    Vector<int32_t> deviceIds;
+    mInputManager->getInputDeviceIds(deviceIds);
+
+    InputDeviceInfo deviceInfo;
+    for (size_t i = 0; i < deviceIds.size(); i++) {
+        int32_t deviceId = deviceIds[i];
+
+        status_t result = mInputManager->getInputDeviceInfo(deviceId, & deviceInfo);
+        if (result == NAME_NOT_FOUND) {
+            continue;
+        } else if (result != OK) {
+            dump.appendFormat("  ** Unexpected error %d getting information about input devices.\n",
+                    result);
+            continue;
+        }
+
+        dump.appendFormat("  Device %d: '%s'\n",
+                deviceInfo.getId(), deviceInfo.getName().string());
+        dump.appendFormat("    sources = 0x%08x\n",
+                deviceInfo.getSources());
+        dump.appendFormat("    keyboardType = %d\n",
+                deviceInfo.getKeyboardType());
+
+        dump.append("    motion ranges:\n");
+        DUMP_MOTION_RANGE(X);
+        DUMP_MOTION_RANGE(Y);
+        DUMP_MOTION_RANGE(PRESSURE);
+        DUMP_MOTION_RANGE(SIZE);
+        DUMP_MOTION_RANGE(TOUCH_MAJOR);
+        DUMP_MOTION_RANGE(TOUCH_MINOR);
+        DUMP_MOTION_RANGE(TOOL_MAJOR);
+        DUMP_MOTION_RANGE(TOOL_MINOR);
+        DUMP_MOTION_RANGE(ORIENTATION);
+    }
+}
+
+#undef DUMP_MOTION_RANGE
+
 void NativeInputManager::logDispatchStateLd() {
     String8 dump;
     dumpDispatchStateLd(dump);
@@ -1899,36 +1937,37 @@
 }
 
 static jint android_server_InputManager_nativeGetScanCodeState(JNIEnv* env, jclass clazz,
-        jint deviceId, jint deviceClasses, jint scanCode) {
+        jint deviceId, jint sourceMask, jint scanCode) {
     if (checkInputManagerUnitialized(env)) {
         return AKEY_STATE_UNKNOWN;
     }
 
     return gNativeInputManager->getInputManager()->getScanCodeState(
-            deviceId, deviceClasses, scanCode);
+            deviceId, uint32_t(sourceMask), scanCode);
 }
 
 static jint android_server_InputManager_nativeGetKeyCodeState(JNIEnv* env, jclass clazz,
-        jint deviceId, jint deviceClasses, jint keyCode) {
+        jint deviceId, jint sourceMask, jint keyCode) {
     if (checkInputManagerUnitialized(env)) {
         return AKEY_STATE_UNKNOWN;
     }
 
     return gNativeInputManager->getInputManager()->getKeyCodeState(
-            deviceId, deviceClasses, keyCode);
+            deviceId, uint32_t(sourceMask), keyCode);
 }
 
 static jint android_server_InputManager_nativeGetSwitchState(JNIEnv* env, jclass clazz,
-        jint deviceId, jint deviceClasses, jint sw) {
+        jint deviceId, jint sourceMask, jint sw) {
     if (checkInputManagerUnitialized(env)) {
         return AKEY_STATE_UNKNOWN;
     }
 
-    return gNativeInputManager->getInputManager()->getSwitchState(deviceId, deviceClasses, sw);
+    return gNativeInputManager->getInputManager()->getSwitchState(
+            deviceId, uint32_t(sourceMask), sw);
 }
 
 static jboolean android_server_InputManager_nativeHasKeys(JNIEnv* env, jclass clazz,
-        jintArray keyCodes, jbooleanArray outFlags) {
+        jint deviceId, jint sourceMask, jintArray keyCodes, jbooleanArray outFlags) {
     if (checkInputManagerUnitialized(env)) {
         return JNI_FALSE;
     }
@@ -1937,8 +1976,9 @@
     uint8_t* flags = env->GetBooleanArrayElements(outFlags, NULL);
     jsize numCodes = env->GetArrayLength(keyCodes);
     jboolean result;
-    if (numCodes == env->GetArrayLength(outFlags)) {
-        result = gNativeInputManager->getInputManager()->hasKeys(numCodes, codes, flags);
+    if (numCodes == env->GetArrayLength(keyCodes)) {
+        result = gNativeInputManager->getInputManager()->hasKeys(
+                deviceId, uint32_t(sourceMask), numCodes, codes, flags);
     } else {
         result = JNI_FALSE;
     }
@@ -2102,7 +2142,7 @@
             (void*) android_server_InputManager_nativeGetKeyCodeState },
     { "nativeGetSwitchState", "(III)I",
             (void*) android_server_InputManager_nativeGetSwitchState },
-    { "nativeHasKeys", "([I[Z)Z",
+    { "nativeHasKeys", "(II[I[Z)Z",
             (void*) android_server_InputManager_nativeHasKeys },
     { "nativeRegisterInputChannel", "(Landroid/view/InputChannel;)V",
             (void*) android_server_InputManager_nativeRegisterInputChannel },
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
new file mode 100644
index 0000000..17a1585
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryListener;
+import android.os.RemoteException;
+import android.test.AndroidTestCase;
+
+public class CountryDetectorServiceTest extends AndroidTestCase {
+    private class CountryListenerTester extends ICountryListener.Stub {
+        private Country mCountry;
+
+        @Override
+        public void onCountryDetected(Country country) throws RemoteException {
+            mCountry = country;
+        }
+
+        public Country getCountry() {
+            return mCountry;
+        }
+
+        public boolean isNotified() {
+            return mCountry != null;
+        }
+    }
+
+    private class CountryDetectorServiceTester extends CountryDetectorService {
+
+        private CountryListener mListener;
+
+        public CountryDetectorServiceTester(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void notifyReceivers(Country country) {
+            super.notifyReceivers(country);
+        }
+
+        @Override
+        protected void setCountryListener(final CountryListener listener) {
+            mListener = listener;
+        }
+
+        public boolean isListenerSet() {
+            return mListener != null;
+        }
+    }
+
+    public void testAddRemoveListener() throws RemoteException {
+        CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
+        serviceTester.systemReady();
+        waitForSystemReady(serviceTester);
+        CountryListenerTester listenerTester = new CountryListenerTester();
+        serviceTester.addCountryListener(listenerTester);
+        assertTrue(serviceTester.isListenerSet());
+        serviceTester.removeCountryListener(listenerTester);
+        assertFalse(serviceTester.isListenerSet());
+    }
+
+    public void testNotifyListeners() throws RemoteException {
+        CountryDetectorServiceTester serviceTester = new CountryDetectorServiceTester(getContext());
+        CountryListenerTester listenerTesterA = new CountryListenerTester();
+        CountryListenerTester listenerTesterB = new CountryListenerTester();
+        Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
+        serviceTester.systemReady();
+        waitForSystemReady(serviceTester);
+        serviceTester.addCountryListener(listenerTesterA);
+        serviceTester.addCountryListener(listenerTesterB);
+        serviceTester.notifyReceivers(country);
+        assertTrue(serviceTester.isListenerSet());
+        assertTrue(listenerTesterA.isNotified());
+        assertTrue(listenerTesterB.isNotified());
+        serviceTester.removeCountryListener(listenerTesterA);
+        serviceTester.removeCountryListener(listenerTesterB);
+        assertFalse(serviceTester.isListenerSet());
+    }
+
+    private void waitForSystemReady(CountryDetectorService service) {
+        int count = 5;
+        while (count-- > 0) {
+            try {
+                Thread.sleep(500);
+            } catch (Exception e) {
+            }
+            if (service.isSystemReady()) {
+                return;
+            }
+        }
+        throw new RuntimeException("Wait System Ready timeout");
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java
new file mode 100644
index 0000000..98966c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/ComprehensiveCountryDetectorTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.location.Country;
+import android.location.CountryListener;
+import android.test.AndroidTestCase;
+
+public class ComprehensiveCountryDetectorTest extends AndroidTestCase {
+    private class TestCountryDetector extends ComprehensiveCountryDetector {
+        public final static String COUNTRY_ISO = "us";
+        private boolean mLocationBasedDetectorStarted;
+        private boolean mLocationBasedDetectorStopped;
+        protected boolean mNotified;
+        private boolean listenerAdded = false;
+
+        private Country mNotifiedCountry;
+        public TestCountryDetector() {
+            super(getContext());
+        }
+
+        public void notifyLocationBasedListener(Country country) {
+            mNotified = true;
+            mNotifiedCountry = country;
+            mLocationBasedCountryDetector.notifyListener(country);
+        }
+
+        public boolean locationBasedDetectorStarted() {
+            return mLocationBasedCountryDetector != null && mLocationBasedDetectorStarted;
+        }
+
+        public boolean locationBasedDetectorStopped() {
+            return mLocationBasedCountryDetector == null && mLocationBasedDetectorStopped;
+        }
+
+        public boolean locationRefreshStarted() {
+            return mLocationRefreshTimer != null;
+        }
+
+        public boolean locationRefreshCancelled() {
+            return mLocationRefreshTimer == null;
+        }
+
+        @Override
+        protected CountryDetectorBase createLocationBasedCountryDetector() {
+            return new CountryDetectorBase(mContext) {
+                @Override
+                public Country detectCountry() {
+                    mLocationBasedDetectorStarted = true;
+                    return null;
+                }
+
+                @Override
+                public void stop() {
+                    mLocationBasedDetectorStopped = true;
+                }
+            };
+        }
+
+        @Override
+        protected Country getNetworkBasedCountry() {
+            return null;
+        }
+
+        @Override
+        protected Country getLastKnownLocationBasedCountry() {
+            return mNotifiedCountry;
+        }
+
+        @Override
+        protected Country getSimBasedCountry() {
+            return null;
+        }
+
+        @Override
+        protected Country getLocaleCountry() {
+            return null;
+        }
+
+        @Override
+        protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+                final boolean notifyChange, final boolean startLocationBasedDetection) {
+            runAfterDetection(country, detectedCountry, notifyChange, startLocationBasedDetection);
+        };
+
+        @Override
+        protected boolean isAirplaneModeOff() {
+            return true;
+        }
+
+        @Override
+        protected synchronized void addPhoneStateListener() {
+            listenerAdded = true;
+        }
+
+        @Override
+        protected synchronized void removePhoneStateListener() {
+            listenerAdded = false;
+        }
+
+        @Override
+        protected boolean isGeoCoderImplemented() {
+            return true;
+        }
+
+        public boolean isPhoneStateListenerAdded() {
+            return listenerAdded;
+        }
+    }
+
+    private class CountryListenerImpl implements CountryListener {
+        private boolean mNotified;
+        private Country mCountry;
+
+        public void onCountryDetected(Country country) {
+            mNotified = true;
+            mCountry = country;
+        }
+
+        public boolean notified() {
+            return mNotified;
+        }
+
+        public Country getCountry() {
+            return mCountry;
+        }
+    }
+
+    public void testDetectNetworkBasedCountry() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_NETWORK);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getNetworkBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertFalse(listener.notified());
+        assertFalse(countryDetector.locationBasedDetectorStarted());
+        assertFalse(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+    }
+
+    public void testDetectLocationBasedCountry() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+        final Country locationBasedCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCATION);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getSimBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.notifyLocationBasedListener(locationBasedCountry);
+        assertTrue(listener.notified());
+        assertTrue(sameCountry(listener.getCountry(), locationBasedCountry));
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testLocaleBasedCountry() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_LOCALE);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getLocaleCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertFalse(listener.notified());
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testStoppingDetector() {
+        // Test stopping detector when LocationBasedCountryDetector was started
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getSimBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.stop();
+        // The LocationBasedDetector should be stopped.
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        // The location refresh should not running.
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testLocationBasedCountryNotFound() {
+        final Country resultCountry = new Country(
+                TestCountryDetector.COUNTRY_ISO, Country.COUNTRY_SOURCE_SIM);
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected Country getSimBasedCountry() {
+                return resultCountry;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, resultCountry));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.notifyLocationBasedListener(null);
+        assertFalse(listener.notified());
+        assertTrue(sameCountry(listener.getCountry(), null));
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testNoCountryFound() {
+        TestCountryDetector countryDetector = new TestCountryDetector();
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        Country country = countryDetector.detectCountry();
+        assertTrue(sameCountry(country, null));
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.notifyLocationBasedListener(null);
+        assertFalse(listener.notified());
+        assertTrue(sameCountry(listener.getCountry(), null));
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+        assertTrue(countryDetector.locationRefreshStarted());
+        countryDetector.stop();
+        assertTrue(countryDetector.locationRefreshCancelled());
+    }
+
+    public void testAddRemoveListener() {
+        TestCountryDetector countryDetector = new TestCountryDetector();
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        assertTrue(countryDetector.isPhoneStateListenerAdded());
+        assertTrue(countryDetector.locationBasedDetectorStarted());
+        countryDetector.setCountryListener(null);
+        assertFalse(countryDetector.isPhoneStateListenerAdded());
+        assertTrue(countryDetector.locationBasedDetectorStopped());
+    }
+
+    public void testGeocoderNotImplemented() {
+        TestCountryDetector countryDetector = new TestCountryDetector() {
+            @Override
+            protected boolean isGeoCoderImplemented() {
+                return false;
+            }
+        };
+        CountryListenerImpl listener = new CountryListenerImpl();
+        countryDetector.setCountryListener(listener);
+        assertTrue(countryDetector.isPhoneStateListenerAdded());
+        assertFalse(countryDetector.locationBasedDetectorStarted());
+        countryDetector.setCountryListener(null);
+        assertFalse(countryDetector.isPhoneStateListenerAdded());
+    }
+
+    private boolean sameCountry(Country country1, Country country2) {
+        return country1 == null && country2 == null || country1 != null && country2 != null &&
+        country1.getCountryIso().equalsIgnoreCase(country2.getCountryIso()) &&
+        country1.getSource() == country2.getSource();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java
new file mode 100755
index 0000000..71e8e2a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/LocationBasedCountryDetectorTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.server.location;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Location;
+import android.location.LocationListener;
+import android.test.AndroidTestCase;
+
+public class LocationBasedCountryDetectorTest extends AndroidTestCase {
+    private class TestCountryDetector extends LocationBasedCountryDetector {
+        public static final int TOTAL_PROVIDERS = 2;
+        protected Object countryFoundLocker = new Object();
+        protected boolean notifyCountry = false;
+        private final Location mLocation;
+        private final String mCountry;
+        private final long mQueryLocationTimeout;
+        private List<LocationListener> mListeners;
+
+        public TestCountryDetector(String country, String provider) {
+            this(country, provider, 1000 * 60 * 5);
+        }
+
+        public TestCountryDetector(String country, String provider, long queryLocationTimeout) {
+            super(getContext());
+            mCountry = country;
+            mLocation = new Location(provider);
+            mQueryLocationTimeout = queryLocationTimeout;
+            mListeners = new ArrayList<LocationListener>();
+        }
+
+        @Override
+        protected String getCountryFromLocation(Location location) {
+            synchronized (countryFoundLocker) {
+                if (!notifyCountry) {
+                    try {
+                        countryFoundLocker.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+            if (mLocation.getProvider().endsWith(location.getProvider())) {
+                return mCountry;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        protected Location getLastKnownLocation() {
+            return mLocation;
+        }
+
+        @Override
+        protected void registerEnabledProviders(List<LocationListener> listeners) {
+            mListeners.addAll(listeners);
+        }
+
+        @Override
+        protected void unregisterProviders(List<LocationListener> listeners) {
+            for (LocationListener listener : mLocationListeners) {
+                assertTrue(mListeners.remove(listener));
+            }
+        }
+
+        @Override
+        protected long getQueryLocationTimeout() {
+            return mQueryLocationTimeout;
+        }
+
+        @Override
+        protected int getTotalEnabledProviders() {
+            return TOTAL_PROVIDERS;
+        }
+
+        public void notifyLocationFound() {
+            // Listener could be removed in the notification.
+            LocationListener[] listeners = new LocationListener[mListeners.size()];
+            mLocationListeners.toArray(listeners);
+            for (LocationListener listener :listeners) {
+                listener.onLocationChanged(mLocation);
+            }
+        }
+
+        public int getListenersCount() {
+            return mListeners.size();
+        }
+
+        public void notifyCountryFound() {
+            synchronized (countryFoundLocker) {
+                notifyCountry = true;
+                countryFoundLocker.notify();
+            }
+        }
+
+        public Timer getTimer() {
+            return mTimer;
+        }
+
+        public Thread getQueryThread() {
+            return mQueryThread;
+        }
+    }
+
+    private class CountryListenerImpl implements CountryListener {
+        private boolean mNotified;
+        private String mCountryCode;
+        public void onCountryDetected(Country country) {
+            mNotified = true;
+            if (country != null) {
+                mCountryCode = country.getCountryIso();
+            }
+        }
+
+        public boolean notified() {
+            return mNotified;
+        }
+
+        public String getCountry() {
+            return mCountryCode;
+        }
+    }
+
+    public void testFindingCountry() {
+        final String country = "us";
+        final String provider = "Good";
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        TestCountryDetector detector = new TestCountryDetector(country, provider);
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.notifyLocationFound();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        assertNull(detector.getTimer());
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        assertTrue(countryListener.notified());
+        assertEquals(countryListener.getCountry(), country);
+    }
+
+    public void testFindingCountryCancelled() {
+        final String country = "us";
+        final String provider = "Good";
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        TestCountryDetector detector = new TestCountryDetector(country, provider);
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.notifyLocationFound();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        // The time should be stopped
+        assertNull(detector.getTimer());
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.stop();
+        // There is no way to stop the thread, let's test it could be stopped, after get country
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        assertTrue(countryListener.notified());
+        assertEquals(countryListener.getCountry(), country);
+    }
+
+    public void testFindingLocationCancelled() {
+        final String country = "us";
+        final String provider = "Good";
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        TestCountryDetector detector = new TestCountryDetector(country, provider);
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.stop();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        // The time should be stopped
+        assertNull(detector.getTimer());
+        // QueryThread should still be NULL
+        assertNull(detector.getQueryThread());
+        assertFalse(countryListener.notified());
+    }
+
+    public void testFindingLocationFailed() {
+        final String country = "us";
+        final String provider = "Good";
+        long timeout = 1000;
+        TestCountryDetector detector = new TestCountryDetector(country, provider, timeout) {
+            @Override
+            protected Location getLastKnownLocation() {
+                return null;
+            }
+        };
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        waitForTimerReset(detector);
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        // QueryThread should still be NULL
+        assertNull(detector.getQueryThread());
+        assertTrue(countryListener.notified());
+        assertNull(countryListener.getCountry());
+    }
+
+    public void testFindingCountryFailed() {
+        final String country = "us";
+        final String provider = "Good";
+        TestCountryDetector detector = new TestCountryDetector(country, provider) {
+            @Override
+            protected String getCountryFromLocation(Location location) {
+                synchronized (countryFoundLocker) {
+                    if (! notifyCountry) {
+                        try {
+                            countryFoundLocker.wait();
+                        } catch (InterruptedException e) {
+                        }
+                    }
+                }
+                // We didn't find country.
+                return null;
+            }
+        };
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        detector.notifyLocationFound();
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        assertNull(detector.getTimer());
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        // CountryListener should be notified
+        assertTrue(countryListener.notified());
+        assertNull(countryListener.getCountry());
+    }
+
+    public void testFindingCountryWithLastKnownLocation() {
+        final String country = "us";
+        final String provider = "Good";
+        long timeout = 1000;
+        TestCountryDetector detector = new TestCountryDetector(country, provider, timeout);
+        CountryListenerImpl countryListener = new CountryListenerImpl();
+        detector.setCountryListener(countryListener);
+        detector.detectCountry();
+        assertEquals(detector.getListenersCount(), TestCountryDetector.TOTAL_PROVIDERS);
+        waitForTimerReset(detector);
+        // All listeners should be unregistered
+        assertEquals(detector.getListenersCount(), 0);
+        Thread queryThread = waitForQueryThreadLaunched(detector);
+        detector.notifyCountryFound();
+        // Wait for query thread ending
+        waitForThreadEnding(queryThread);
+        // QueryThread should be set to NULL
+        assertNull(detector.getQueryThread());
+        // CountryListener should be notified
+        assertTrue(countryListener.notified());
+        assertEquals(countryListener.getCountry(), country);
+    }
+
+    private void waitForTimerReset(TestCountryDetector detector) {
+        int count = 5;
+        long interval = 1000;
+        try {
+            while (count-- > 0 && detector.getTimer() != null) {
+                Thread.sleep(interval);
+            }
+        } catch (InterruptedException e) {
+        }
+        Timer timer = detector.getTimer();
+        assertTrue(timer == null);
+    }
+
+    private void waitForThreadEnding(Thread thread) {
+        try {
+            thread.join(5000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private Thread waitForQueryThreadLaunched(TestCountryDetector detector) {
+        int count = 5;
+        long interval = 1000;
+        try {
+            while (count-- > 0 && detector.getQueryThread() == null) {
+                Thread.sleep(interval);
+            }
+        } catch (InterruptedException e) {
+        }
+        Thread thread = detector.getQueryThread();
+        assertTrue(thread != null);
+        return thread;
+    }
+}
diff --git a/test-runner/src/android/test/LoaderTestCase.java b/test-runner/src/android/test/LoaderTestCase.java
new file mode 100644
index 0000000..8be6590
--- /dev/null
+++ b/test-runner/src/android/test/LoaderTestCase.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.test;
+
+import android.content.Loader;
+import android.content.Loader.OnLoadCompleteListener;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * A convenience class for testing {@link Loader}s. This test case
+ * provides a simple way to synchronously get the result from a Loader making
+ * it easy to assert that the Loader returns the expected result.
+ */
+public class LoaderTestCase extends AndroidTestCase {
+    static {
+        // Force class loading of AsyncTask on the main thread so that it's handlers are tied to
+        // the main thread and responses from the worker thread get delivered on the main thread.
+        // The tests are run on another thread, allowing them to block waiting on a response from
+        // the code running on the main thread. The main thread can't block since the AysncTask
+        // results come in via the event loop.
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... args) {return null;}
+            @Override
+            protected void onPostExecute(Void result) {}
+        };
+    }
+
+    /**
+     * Runs a Loader synchronously and returns the result of the load. The loader will
+     * be started, stopped, and destroyed by this method so it cannot be reused.
+     *
+     * @param loader The loader to run synchronously
+     * @return The result from the loader
+     */
+    public <T> T getLoaderResultSynchronously(final Loader<T> loader) {
+        // The test thread blocks on this queue until the loader puts it's result in
+        final ArrayBlockingQueue<T> queue = new ArrayBlockingQueue<T>(1);
+
+        // This callback runs on the "main" thread and unblocks the test thread
+        // when it puts the result into the blocking queue
+        final OnLoadCompleteListener<T> listener = new OnLoadCompleteListener<T>() {
+            @Override
+            public void onLoadComplete(Loader<T> completedLoader, T data) {
+                // Shut the loader down
+                completedLoader.unregisterListener(this);
+                completedLoader.stopLoading();
+                completedLoader.destroy();
+
+                // Store the result, unblocking the test thread
+                queue.add(data);
+            }
+        };
+
+        // This handler runs on the "main" thread of the process since AsyncTask
+        // is documented as needing to run on the main thread and many Loaders use
+        // AsyncTask
+        final Handler mainThreadHandler = new Handler(Looper.getMainLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                loader.registerListener(0, listener);
+                loader.startLoading();
+            }
+        };
+
+        // Ask the main thread to start the loading process
+        mainThreadHandler.sendEmptyMessage(0);
+
+        // Block on the queue waiting for the result of the load to be inserted
+        T result;
+        while (true) {
+            try {
+                result = queue.take();
+                break;
+            } catch (InterruptedException e) {
+                throw new RuntimeException("waiting thread interrupted", e);
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 4a22b68..6fac902 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -165,7 +165,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_DISABLING = 0;
+    public static final int WIFI_AP_STATE_DISABLING = 10;
     /**
      * Wi-Fi AP is disabled.
      *
@@ -174,7 +174,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_DISABLED = 1;
+    public static final int WIFI_AP_STATE_DISABLED = 11;
     /**
      * Wi-Fi AP is currently being enabled. The state will change to
      * {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully.
@@ -184,7 +184,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_ENABLING = 2;
+    public static final int WIFI_AP_STATE_ENABLING = 12;
     /**
      * Wi-Fi AP is enabled.
      *
@@ -193,7 +193,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_ENABLED = 3;
+    public static final int WIFI_AP_STATE_ENABLED = 13;
     /**
      * Wi-Fi AP is in a failed state. This state will occur when an error occurs during
      * enabling or disabling
@@ -203,7 +203,7 @@
      *
      * @hide
      */
-    public static final int WIFI_AP_STATE_FAILED = 4;
+    public static final int WIFI_AP_STATE_FAILED = 14;
 
     /**
      * Broadcast intent action indicating that a connection to the supplicant has
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 266d801..f2f8343 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -155,7 +155,7 @@
         public MonitorThread() {
             super("WifiMonitor");
         }
-        
+
         public void run() {
 
             if (connectToSupplicant()) {
@@ -272,7 +272,7 @@
             int connectTries = 0;
 
             while (true) {
-                if (mWifiStateTracker.connectToSupplicant()) {
+                if (WifiNative.connectToSupplicant()) {
                     return true;
                 }
                 if (connectTries++ < 3) {
@@ -375,7 +375,7 @@
             if (newSupplicantState == SupplicantState.INVALID) {
                 Log.w(TAG, "Invalid supplicant state: " + newState);
             }
-            mWifiStateTracker.notifyStateChange(networkId, BSSID, newSupplicantState);
+            mWifiStateTracker.notifySupplicantStateChange(networkId, BSSID, newSupplicantState);
         }
     }
 
@@ -395,7 +395,7 @@
                 }
             }
         }
-        mWifiStateTracker.notifyStateChange(newState, BSSID, networkId);
+        mWifiStateTracker.notifyNetworkStateChange(newState, BSSID, networkId);
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index 7a3282c..47f8813 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -41,6 +41,8 @@
     public native static String getErrorString(int errorCode);
 
     public native static boolean loadDriver();
+
+    public native static boolean isDriverLoaded();
     
     public native static boolean unloadDriver();
 
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 5780a04..4fb35c9 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +22,21 @@
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
 import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
+/**
+ * TODO: Add soft AP states as part of WIFI_STATE_XXX
+ * Retain WIFI_STATE_ENABLING that indicates driver is loading
+ * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started
+ * and WIFI_STATE_FAILED for failure
+ * Deprecate WIFI_STATE_UNKNOWN
+ *
+ * Doing this will simplify the logic for sending broadcasts
+ */
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
 import android.app.ActivityManagerNative;
 import android.net.NetworkInfo;
 import android.net.NetworkStateTracker;
@@ -31,21 +46,25 @@
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
 import android.net.NetworkProperties;
+import android.os.Binder;
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.PowerManager;
 import android.os.SystemProperties;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.Process;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
-import android.util.Config;
+import android.util.Slog;
 import android.app.Notification;
 import android.app.PendingIntent;
+import android.app.backup.IBackupManager;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothA2dp;
@@ -54,15 +73,22 @@
 import android.content.Context;
 import android.database.ContentObserver;
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
 
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
 
 /**
  * Track the state of Wifi connectivity. All event handling is done here,
@@ -70,165 +96,61 @@
  *
  * @hide
  */
-public class WifiStateTracker extends Handler implements NetworkStateTracker {
+//TODO: we still need frequent scanning for the case when
+// we issue disconnect but need scan results for open network notification
+public class WifiStateTracker extends HierarchicalStateMachine implements NetworkStateTracker {
 
-    private static final boolean LOCAL_LOGD = Config.LOGD || false;
-    
     private static final String TAG = "WifiStateTracker";
+    private static final String NETWORKTYPE = "WIFI";
+    private static final boolean DBG = false;
 
-    // Event log tags (must be in sync with event-log-tags)
-    private static final int EVENTLOG_NETWORK_STATE_CHANGED = 50021;
-    private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50022;
-    private static final int EVENTLOG_DRIVER_STATE_CHANGED = 50023;
-    private static final int EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED = 50024;
-    private static final int EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED = 50025;
-
-    // Event codes
-    private static final int EVENT_SUPPLICANT_CONNECTION             = 1;
-    private static final int EVENT_SUPPLICANT_DISCONNECT             = 2;
-    private static final int EVENT_SUPPLICANT_STATE_CHANGED          = 3;
-    private static final int EVENT_NETWORK_STATE_CHANGED             = 4;
-    private static final int EVENT_SCAN_RESULTS_AVAILABLE            = 5;
-    private static final int EVENT_INTERFACE_CONFIGURATION_SUCCEEDED = 6;
-    private static final int EVENT_INTERFACE_CONFIGURATION_FAILED    = 7;
-    private static final int EVENT_POLL_INTERVAL                     = 8;
-    private static final int EVENT_DHCP_START                        = 9;
-    private static final int EVENT_DEFERRED_DISCONNECT               = 10;
-    private static final int EVENT_DEFERRED_RECONNECT                = 11;
-    /**
-     * The driver is started or stopped. The object will be the state: true for
-     * started, false for stopped.
-     */
-    private static final int EVENT_DRIVER_STATE_CHANGED              = 12;
-    private static final int EVENT_PASSWORD_KEY_MAY_BE_INCORRECT     = 13;
-    private static final int EVENT_MAYBE_START_SCAN_POST_DISCONNECT  = 14;
-
-    /**
-     * The driver state indication.
-     */
-    private static final int DRIVER_STARTED                          = 0;
-    private static final int DRIVER_STOPPED                          = 1;
-    private static final int DRIVER_HUNG                             = 2;
-
-    /**
-     * Interval in milliseconds between polling for connection
-     * status items that are not sent via asynchronous events.
-     * An example is RSSI (signal strength).
-     */
-    private static final int POLL_STATUS_INTERVAL_MSECS = 3000;
-
-    /**
-     * The max number of the WPA supplicant loop iterations before we
-     * decide that the loop should be terminated:
-     */
-    private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
-
-    /**
-     * When a DISCONNECT event is received, we defer handling it to
-     * allow for the possibility that the DISCONNECT is about to
-     * be followed shortly by a CONNECT to the same network we were
-     * just connected to. In such a case, we don't want to report
-     * the network as down, nor do we want to reconfigure the network
-     * interface, etc. If we get a CONNECT event for another network
-     * within the delay window, we immediately handle the pending
-     * disconnect before processing the CONNECT.<p/>
-     * The five second delay is chosen somewhat arbitrarily, but is
-     * meant to cover most of the cases where a DISCONNECT/CONNECT
-     * happens to a network.
-     */
-    private static final int DISCONNECT_DELAY_MSECS = 5000;
-    /**
-     * When the supplicant goes idle after we do an explicit disconnect
-     * following a DHCP failure, we need to kick the supplicant into
-     * trying to associate with access points.
-     */
-    private static final int RECONNECT_DELAY_MSECS = 2000;
-
-    /**
-     * When the supplicant disconnects from an AP it sometimes forgets
-     * to restart scanning.  Wait this delay before asking it to start
-     * scanning (in case it forgot).  15 sec is the standard delay between
-     * scans.
-     */
-    private static final int KICKSTART_SCANNING_DELAY_MSECS = 15000;
-
-    /**
-     * The maximum number of times we will retry a connection to an access point
-     * for which we have failed in acquiring an IP address from DHCP. A value of
-     * N means that we will make N+1 connection attempts in all.
-     * <p>
-     * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
-     * value if a Settings value is not present.
-     */
-    private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
-
-    private static final int DRIVER_POWER_MODE_AUTO = 0;
-    private static final int DRIVER_POWER_MODE_ACTIVE = 1;
-
-    /**
-     * The current WPA supplicant loop state (used to detect looping behavior):
-     */
-    private SupplicantState mSupplicantLoopState = SupplicantState.DISCONNECTED;
-
-    /**
-     * The current number of WPA supplicant loop iterations:
-     */
-    private int mNumSupplicantLoopIterations = 0;
-
-    /**
-     * The current number of supplicant state changes.  This is used to determine
-     * if we've received any new info since we found out it was DISCONNECTED or
-     * INACTIVE.  If we haven't for X ms, we then request a scan - it should have
-     * done that automatically, but sometimes some firmware does not.
-     */
-    private int mNumSupplicantStateChanges = 0;
-
-    /**
-     * True if we received an event that that a password-key may be incorrect.
-     * If the next incoming supplicant state change event is DISCONNECT,
-     * broadcast a message that we have a possible password error and disable
-     * the network.
-     */
-    private boolean mPasswordKeyMayBeIncorrect = false;
-
-    public static final int SUPPL_SCAN_HANDLING_NORMAL = 1;
-    public static final int SUPPL_SCAN_HANDLING_LIST_ONLY = 2;
+    /* TODO: fetch a configurable interface */
+    private static final String SOFTAP_IFACE = "wl0.1";
 
     private WifiMonitor mWifiMonitor;
-    private WifiInfo mWifiInfo;
+    private INetworkManagementService nwService;
+    private ConnectivityManager mCm;
+
+    /* Scan results handling */
     private List<ScanResult> mScanResults;
-    private WifiManager mWM;
-    private boolean mHaveIpAddress;
-    private boolean mObtainingIpAddress;
-    private boolean mTornDownByConnMgr;
-    private NetworkInfo mNetworkInfo;
-    private boolean mTeardownRequested = false;
-    /**
-     * A DISCONNECT event has been received, but processing it
-     * is being deferred.
-     */
-    private boolean mDisconnectPending;
-    /**
-     * An operation has been performed as a result of which we expect the next event
-     * will be a DISCONNECT.
-     */
-    private boolean mDisconnectExpected;
-    private DhcpHandler mDhcpTarget;
-    private DhcpInfo mDhcpInfo;
-    private NetworkProperties mNetworkProperties;
+    private static final Pattern scanResultPattern = Pattern.compile("\t+");
+    private static final int SCAN_RESULT_CACHE_SIZE = 80;
+    private final LinkedHashMap<String, ScanResult> mScanResultCache;
+
+    private String mInterfaceName;
+    private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
+    private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
+    private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
+    private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
+
+    private int mNumAllowedChannels = 0;
     private int mLastSignalLevel = -1;
     private String mLastBssid;
-    private String mLastSsid;
-    private int mLastNetworkId = -1;
+    private int mLastNetworkId;
+    private boolean mEnableRssiPolling = false;
+    private boolean mPasswordKeyMayBeIncorrect = false;
     private boolean mUseStaticIp = false;
-    private int mReconnectCount;
+    private int mReconnectCount = 0;
+    private boolean mIsScanMode = false;
 
-    // used to store the (non-persisted) num determined during device boot 
-    // (from mcc or other phone info) before the driver is started.
-    private int mNumAllowedChannels = 0;
+    /**
+     * Instance of the bluetooth headset helper. This needs to be created
+     * early because there is a delay before it actually 'connects', as
+     * noted by its javadoc. If we check before it is connected, it will be
+     * in an error state and we will not disable coexistence.
+     */
+    private BluetoothHeadset mBluetoothHeadset;
+
+    private BluetoothA2dp mBluetoothA2dp;
+
+    /**
+     * Observes the static IP address settings.
+     */
+    private SettingsObserver mSettingsObserver;
+    private NetworkProperties mNetworkProperties;
+
 
     // Variables relating to the 'available networks' notification
-    
     /**
      * The icon to show in the 'available networks' notification. This will also
      * be the ID of the Notification given to the NotificationManager.
@@ -276,13 +198,230 @@
      * something other than scanning, we reset this to 0.
      */
     private int mNumScansSinceNetworkStateChange;
-    /**
-     * Observes the static IP address settings.
+
+    // Held during driver load and unload
+    private static PowerManager.WakeLock sWakeLock;
+
+    /* For sending events to connectivity service handler */
+    private Handler mCsHandler;
+    private Context mContext;
+
+    private DhcpInfo mDhcpInfo;
+    private WifiInfo mWifiInfo;
+    private NetworkInfo mNetworkInfo;
+    private SupplicantStateTracker mSupplicantStateTracker;
+
+    // Event log tags (must be in sync with event-log-tags)
+    private static final int EVENTLOG_WIFI_STATE_CHANGED        = 50021;
+    private static final int EVENTLOG_WIFI_EVENT_HANDLED        = 50022;
+    private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED  = 50023;
+
+    /* Load the driver */
+    private static final int CMD_LOAD_DRIVER                      = 1;
+    /* Unload the driver */
+    private static final int CMD_UNLOAD_DRIVER                    = 2;
+    /* Indicates driver load succeeded */
+    private static final int CMD_LOAD_DRIVER_SUCCESS              = 3;
+    /* Indicates driver load failed */
+    private static final int CMD_LOAD_DRIVER_FAILURE              = 4;
+    /* Indicates driver unload succeeded */
+    private static final int CMD_UNLOAD_DRIVER_SUCCESS            = 5;
+    /* Indicates driver unload failed */
+    private static final int CMD_UNLOAD_DRIVER_FAILURE            = 6;
+
+    /* Start the supplicant */
+    private static final int CMD_START_SUPPLICANT                 = 11;
+    /* Stop the supplicant */
+    private static final int CMD_STOP_SUPPLICANT                  = 12;
+    /* Start the driver */
+    private static final int CMD_START_DRIVER                     = 13;
+    /* Start the driver */
+    private static final int CMD_STOP_DRIVER                      = 14;
+    /* Indicates DHCP succeded */
+    private static final int CMD_IP_CONFIG_SUCCESS                = 15;
+    /* Indicates DHCP failed */
+    private static final int CMD_IP_CONFIG_FAILURE                = 16;
+    /* Re-configure interface */
+    private static final int CMD_RECONFIGURE_IP                   = 17;
+
+
+    /* Start the soft access point */
+    private static final int CMD_START_AP                         = 21;
+    /* Stop the soft access point */
+    private static final int CMD_STOP_AP                          = 22;
+
+
+    /* Supplicant events */
+    /* Connection to supplicant established */
+    private static final int SUP_CONNECTION_EVENT                 = 31;
+    /* Connection to supplicant lost */
+    private static final int SUP_DISCONNECTION_EVENT              = 32;
+    /* Driver start completed */
+    private static final int DRIVER_START_EVENT                   = 33;
+    /* Driver stop completed */
+    private static final int DRIVER_STOP_EVENT                    = 34;
+    /* Network connection completed */
+    private static final int NETWORK_CONNECTION_EVENT             = 36;
+    /* Network disconnection completed */
+    private static final int NETWORK_DISCONNECTION_EVENT          = 37;
+    /* Scan results are available */
+    private static final int SCAN_RESULTS_EVENT                   = 38;
+    /* Supplicate state changed */
+    private static final int SUPPLICANT_STATE_CHANGE_EVENT        = 39;
+    /* Password may be incorrect */
+    private static final int PASSWORD_MAY_BE_INCORRECT_EVENT      = 40;
+
+    /* Supplicant commands */
+    /* Is supplicant alive ? */
+    private static final int CMD_PING_SUPPLICANT                  = 51;
+    /* Add/update a network configuration */
+    private static final int CMD_ADD_OR_UPDATE_NETWORK            = 52;
+    /* Delete a network */
+    private static final int CMD_REMOVE_NETWORK                   = 53;
+    /* Enable a network. The device will attempt a connection to the given network. */
+    private static final int CMD_ENABLE_NETWORK                   = 54;
+    /* Disable a network. The device does not attempt a connection to the given network. */
+    private static final int CMD_DISABLE_NETWORK                  = 55;
+    /* Blacklist network. De-prioritizes the given BSSID for connection. */
+    private static final int CMD_BLACKLIST_NETWORK                = 56;
+    /* Clear the blacklist network list */
+    private static final int CMD_CLEAR_BLACKLIST                  = 57;
+    /* Get the configured networks */
+    private static final int CMD_GET_NETWORK_CONFIG               = 58;
+    /* Save configuration */
+    private static final int CMD_SAVE_CONFIG                      = 59;
+    /* Connection status */
+    private static final int CMD_CONNECTION_STATUS                = 60;
+
+    /* Supplicant commands after driver start*/
+    /* Initiate a scan */
+    private static final int CMD_START_SCAN                       = 71;
+    /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */
+    private static final int CMD_SET_SCAN_MODE                    = 72;
+    /* Set scan type. SCAN_ACTIVE or SCAN_PASSIVE */
+    private static final int CMD_SET_SCAN_TYPE                    = 73;
+    /* Disconnect from a network */
+    private static final int CMD_DISCONNECT                       = 74;
+    /* Reconnect to a network */
+    private static final int CMD_RECONNECT                        = 75;
+    /* Reassociate to a network */
+    private static final int CMD_REASSOCIATE                      = 76;
+    /* Set power mode
+     * POWER_MODE_ACTIVE
+     * POWER_MODE_AUTO
      */
-    private SettingsObserver mSettingsObserver;
-    
-    private boolean mIsScanModeActive;
-    private boolean mEnableRssiPolling;
+    private static final int CMD_SET_POWER_MODE                   = 77;
+    /* Set bluetooth co-existence
+     * BLUETOOTH_COEXISTENCE_MODE_ENABLED
+     * BLUETOOTH_COEXISTENCE_MODE_DISABLED
+     * BLUETOOTH_COEXISTENCE_MODE_SENSE
+     */
+    private static final int CMD_SET_BLUETOOTH_COEXISTENCE        = 78;
+    /* Enable/disable bluetooth scan mode
+     * true(1)
+     * false(0)
+     */
+    private static final int CMD_SET_BLUETOOTH_SCAN_MODE          = 79;
+    /* Set number of allowed channels */
+    private static final int CMD_SET_NUM_ALLOWED_CHANNELS         = 80;
+    /* Request connectivity manager wake lock before driver stop */
+    private static final int CMD_REQUEST_CM_WAKELOCK              = 81;
+    /* Enables RSSI poll */
+    private static final int CMD_ENABLE_RSSI_POLL                 = 82;
+    /* RSSI poll */
+    private static final int CMD_RSSI_POLL                        = 83;
+    /* Get current RSSI */
+    private static final int CMD_GET_RSSI                         = 84;
+    /* Get approx current RSSI */
+    private static final int CMD_GET_RSSI_APPROX                  = 85;
+    /* Get link speed on connection */
+    private static final int CMD_GET_LINK_SPEED                   = 86;
+    /* Radio mac address */
+    private static final int CMD_GET_MAC_ADDR                     = 87;
+    /* Set up packet filtering */
+    private static final int CMD_START_PACKET_FILTERING           = 88;
+    /* Clear packet filter */
+    private static final int CMD_STOP_PACKET_FILTERING            = 89;
+
+    /* Connectivity service commands */
+    /* Bring down wifi connection */
+    private static final int CM_CMD_TEARDOWN                      = 110;
+    /* Reconnect to wifi */
+    private static final int CM_CMD_RECONNECT                     = 111;
+
+    /**
+     * Interval in milliseconds between polling for connection
+     * status items that are not sent via asynchronous events.
+     * An example is RSSI (signal strength).
+     */
+    private static final int POLL_RSSI_INTERVAL_MSECS = 3000;
+
+    private static final int CONNECT_MODE   = 1;
+    private static final int SCAN_ONLY_MODE = 2;
+
+    private static final int SCAN_ACTIVE = 1;
+    private static final int SCAN_PASSIVE = 2;
+
+    /**
+     * The maximum number of times we will retry a connection to an access point
+     * for which we have failed in acquiring an IP address from DHCP. A value of
+     * N means that we will make N+1 connection attempts in all.
+     * <p>
+     * See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default
+     * value if a Settings value is not present.
+     */
+    private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
+
+    private static final int DRIVER_POWER_MODE_ACTIVE = 1;
+    private static final int DRIVER_POWER_MODE_AUTO = 0;
+
+    /* Default parent state */
+    private HierarchicalState mDefaultState = new DefaultState();
+    /* Temporary initial state */
+    private HierarchicalState mInitialState = new InitialState();
+    /* Unloading the driver */
+    private HierarchicalState mDriverUnloadingState = new DriverUnloadingState();
+    /* Loading the driver */
+    private HierarchicalState mDriverUnloadedState = new DriverUnloadedState();
+    /* Driver load/unload failed */
+    private HierarchicalState mDriverFailedState = new DriverFailedState();
+    /* Driver loading */
+    private HierarchicalState mDriverLoadingState = new DriverLoadingState();
+    /* Driver loaded */
+    private HierarchicalState mDriverLoadedState = new DriverLoadedState();
+    /* Driver loaded, waiting for supplicant to start */
+    private HierarchicalState mWaitForSupState = new WaitForSupState();
+
+    /* Driver loaded and supplicant ready */
+    private HierarchicalState mDriverSupReadyState = new DriverSupReadyState();
+    /* Driver start issued, waiting for completed event */
+    private HierarchicalState mDriverStartingState = new DriverStartingState();
+    /* Driver started */
+    private HierarchicalState mDriverStartedState = new DriverStartedState();
+    /* Driver stopping */
+    private HierarchicalState mDriverStoppingState = new DriverStoppingState();
+    /* Driver stopped */
+    private HierarchicalState mDriverStoppedState = new DriverStoppedState();
+    /* Scan for networks, no connection will be established */
+    private HierarchicalState mScanModeState = new ScanModeState();
+    /* Connecting to an access point */
+    private HierarchicalState mConnectModeState = new ConnectModeState();
+    /* Fetching IP after network connection (assoc+auth complete) */
+    private HierarchicalState mConnectingState = new ConnectingState();
+    /* Connected with IP addr */
+    private HierarchicalState mConnectedState = new ConnectedState();
+    /* disconnect issued, waiting for network disconnect confirmation */
+    private HierarchicalState mDisconnectingState = new DisconnectingState();
+    /* Network is not connected, supplicant assoc+auth is not complete */
+    private HierarchicalState mDisconnectedState = new DisconnectedState();
+
+    /* Soft Ap is running */
+    private HierarchicalState mSoftApStartedState = new SoftApStartedState();
+
+    /* Argument for Message object to indicate a synchronous call */
+    private static final int SYNCHRONOUS_CALL = 1;
+    private static final int ASYNCHRONOUS_CALL = 0;
+
 
     /**
      * One of  {@link WifiManager#WIFI_STATE_DISABLED},
@@ -291,242 +430,153 @@
      *         {@link WifiManager#WIFI_STATE_ENABLING},
      *         {@link WifiManager#WIFI_STATE_UNKNOWN}
      *
-     * getWifiState() is not synchronized to make sure it's always fast,
-     * even when the instance lock is held on other slow operations.
-     * Use a atomic variable for state.
      */
-    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_UNKNOWN);
+    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
 
-    // Wi-Fi run states:
-    private static final int RUN_STATE_STARTING = 1;
-    private static final int RUN_STATE_RUNNING  = 2;
-    private static final int RUN_STATE_STOPPING = 3;
-    private static final int RUN_STATE_STOPPED  = 4;
+    /**
+     * One of  {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     *
+     */
+    private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
 
-    private static final String mRunStateNames[] = {
-            "Starting",
-            "Running",
-            "Stopping",
-            "Stopped"
-    };
-    private int mRunState;
+    private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
+    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
 
     private final IBatteryStats mBatteryStats;
 
-    private boolean mIsScanOnly;
-
-    private BluetoothA2dp mBluetoothA2dp;
-
-    private String mInterfaceName;
-    private static String LS = System.getProperty("line.separator");
-
-    private Handler mTarget;
-    private Context mContext;
-    private boolean mPrivateDnsRouteSet = false;
-    private int mDefaultGatewayAddr = 0;
-    private boolean mDefaultRouteSet = false;
-
-    /**
-     * A structure for supplying information about a supplicant state
-     * change in the STATE_CHANGE event message that comes from the
-     * WifiMonitor
-     * thread.
-     */
-    private static class SupplicantStateChangeResult {
-        SupplicantStateChangeResult(int networkId, String BSSID, SupplicantState state) {
-            this.state = state;
-            this.BSSID = BSSID;
-            this.networkId = networkId;
-        }
-        int networkId;
-        String BSSID;
-        SupplicantState state;
-    }
-
-    /**
-     * A structure for supplying information about a connection in
-     * the CONNECTED event message that comes from the WifiMonitor
-     * thread.
-     */
-    private static class NetworkStateChangeResult {
-        NetworkStateChangeResult(DetailedState state, String BSSID, int networkId) {
-            this.state = state;
-            this.BSSID = BSSID;
-            this.networkId = networkId;
-        }
-        DetailedState state;
-        String BSSID;
-        int networkId;
-    }
-
     public WifiStateTracker(Context context, Handler target) {
-        mTarget = target;
+        super(NETWORKTYPE);
+
+        mCsHandler = target;
         mContext = context;
-        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
-        mWifiInfo = new WifiInfo();
+
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        nwService = INetworkManagementService.Stub.asInterface(b);
+
         mWifiMonitor = new WifiMonitor(this);
-        mHaveIpAddress = false;
-        mObtainingIpAddress = false;
-        setTornDownByConnMgr(false);
-        mDisconnectPending = false;
-        mScanResults = new ArrayList<ScanResult>();
-        // Allocate DHCP info object once, and fill it in on each request
         mDhcpInfo = new DhcpInfo();
-        mRunState = RUN_STATE_STARTING;
+        mWifiInfo = new WifiInfo();
+        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
+        mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler());
+
+        mBluetoothHeadset = new BluetoothHeadset(mContext, null);
+        mNetworkProperties = new NetworkProperties();
+
+        mNetworkInfo.setIsAvailable(false);
+        mNetworkProperties.clear();
+        resetNotificationTimer();
+        setTeardownRequested(false);
+        mLastBssid = null;
+        mLastNetworkId = -1;
+        mLastSignalLevel = -1;
+
+        mScanResultCache = new LinkedHashMap<String, ScanResult>(
+            SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
+                /*
+                 * Limit the cache size by SCAN_RESULT_CACHE_SIZE
+                 * elements
+                 */
+                @Override
+                public boolean removeEldestEntry(Map.Entry eldest) {
+                    return SCAN_RESULT_CACHE_SIZE < this.size();
+                }
+        };
 
         // Setting is in seconds
-        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(), 
+        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
                 Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
-        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(target);
         mNotificationEnabledSettingObserver.register();
 
         mSettingsObserver = new SettingsObserver(new Handler());
 
-        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
-        mNetworkProperties = new NetworkProperties();
-        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
 
+        addState(mDefaultState);
+            addState(mInitialState, mDefaultState);
+            addState(mDriverUnloadingState, mDefaultState);
+            addState(mDriverUnloadedState, mDefaultState);
+                addState(mDriverFailedState, mDriverUnloadedState);
+            addState(mDriverLoadingState, mDefaultState);
+            addState(mDriverLoadedState, mDefaultState);
+                addState(mWaitForSupState, mDriverLoadedState);
+            addState(mDriverSupReadyState, mDefaultState);
+                addState(mDriverStartingState, mDriverSupReadyState);
+                addState(mDriverStartedState, mDriverSupReadyState);
+                    addState(mScanModeState, mDriverStartedState);
+                    addState(mConnectModeState, mDriverStartedState);
+                        addState(mConnectingState, mConnectModeState);
+                        addState(mConnectedState, mConnectModeState);
+                        addState(mDisconnectingState, mConnectModeState);
+                        addState(mDisconnectedState, mConnectModeState);
+                addState(mDriverStoppingState, mDriverSupReadyState);
+                addState(mDriverStoppedState, mDriverSupReadyState);
+            addState(mSoftApStartedState, mDefaultState);
+
+        setInitialState(mInitialState);
+
+        if (DBG) setDbg(true);
+
+        //start the state machine
+        start();
     }
 
-    /**
-     * Helper method: sets the supplicant state and keeps the network
-     * info updated.
-     * @param state the new state
-     */
-    private void setSupplicantState(SupplicantState state) {
-        mWifiInfo.setSupplicantState(state);
-        updateNetworkInfo();
-        checkPollTimer();
-    }
 
-    public SupplicantState getSupplicantState() {
-        return mWifiInfo.getSupplicantState();
-    }
-
-    /**
-     * Helper method: sets the supplicant state and keeps the network
-     * info updated (string version).
-     * @param stateName the string name of the new state
-     */
-    private void setSupplicantState(String stateName) {
-        mWifiInfo.setSupplicantState(stateName);
-        updateNetworkInfo();
-        checkPollTimer();
-    }
-
-    /**
-     * Record the detailed state of a network, and if it is a
-     * change from the previous state, send a notification to
-     * any listeners.
-     * @param state the new @{code DetailedState}
-     */
-    private void setDetailedState(NetworkInfo.DetailedState state) {
-        setDetailedState(state, null, null);
-    }
-
-    /**
-     * Record the detailed state of a network, and if it is a
-     * change from the previous state, send a notification to
-     * any listeners.
-     * @param state the new @{code DetailedState}
-     * @param reason a {@code String} indicating a reason for the state change,
-     * if one was supplied. May be {@code null}.
-     * @param extraInfo optional {@code String} providing extra information about the state change
-     */
-    private void setDetailedState(NetworkInfo.DetailedState state, String reason, String extraInfo) {
-        if (LOCAL_LOGD) Log.d(TAG, "setDetailed state, old ="
-                + mNetworkInfo.getDetailedState() + " and new state=" + state);
-        if (state != mNetworkInfo.getDetailedState()) {
-            boolean wasConnecting = (mNetworkInfo.getState() == NetworkInfo.State.CONNECTING);
-            String lastReason = mNetworkInfo.getReason();
-            /*
-             * If a reason was supplied when the CONNECTING state was entered, and no
-             * reason was supplied for entering the CONNECTED state, then retain the
-             * reason that was supplied when going to CONNECTING.
-             */
-            if (wasConnecting && state == NetworkInfo.DetailedState.CONNECTED && reason == null
-                    && lastReason != null)
-                reason = lastReason;
-            mNetworkInfo.setDetailedState(state, reason, extraInfo);
-            Message msg = mTarget.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
-            msg.sendToTarget();
-        }
-    }
-
-    private void setDetailedStateInternal(NetworkInfo.DetailedState state) {
-        mNetworkInfo.setDetailedState(state, null, null);
-    }
+    /*********************************************************
+     * NetworkStateTracker interface implementation
+     ********************************************************/
 
     public void setTeardownRequested(boolean isRequested) {
-        mTeardownRequested = isRequested;
+        mTeardownRequested.set(isRequested);
     }
 
     public boolean isTeardownRequested() {
-        return mTeardownRequested;
+        return mTeardownRequested.get();
     }
 
     /**
-     * Helper method: sets the boolean indicating that the connection
-     * manager asked the network to be torn down (and so only the connection
-     * manager can set it up again).
-     * network info updated.
-     * @param flag {@code true} if explicitly disabled.
+     * Begin monitoring wifi connectivity
+     * @deprecated
+     *
+     * TODO: remove this from callers
      */
-    private void setTornDownByConnMgr(boolean flag) {
-        mTornDownByConnMgr = flag;
-        updateNetworkInfo();
-    }
-
-    /**
-     * Return the name of our WLAN network interface.
-     * @return the name of our interface.
-     */
-    public String getInterfaceName() {
-        return mInterfaceName;
-    }
-
-    public boolean isPrivateDnsRouteSet() {
-        return mPrivateDnsRouteSet;
-    }
-
-    public void privateDnsRouteSet(boolean enabled) {
-        mPrivateDnsRouteSet = enabled;
-    }
-
-    public NetworkInfo getNetworkInfo() {
-        return mNetworkInfo;
-    }
-
-    public int getDefaultGatewayAddr() {
-        return mDefaultGatewayAddr;
-    }
-
-    public boolean isDefaultRouteSet() {
-        return mDefaultRouteSet;
-    }
-
-    public void defaultRouteSet(boolean enabled) {
-        mDefaultRouteSet = enabled;
-    }
-
-    /**
-     * Return the system properties name associated with the tcp buffer sizes
-     * for this network.
-     */
-    public String getTcpBufferSizesPropName() {
-        return "net.tcp.buffersize.wifi";
-    }
-
     public void startMonitoring() {
-        /*
-         * Get a handle on the WifiManager. This cannot be done in our
-         * constructor, because the Wifi service is not yet registered.
-         */
-        mWM = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
     }
 
-    public void startEventLoop() {
-        mWifiMonitor.startMonitoring();
+    /**
+     * Disable connectivity to a network
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean teardown() {
+        sendMessage(CM_CMD_TEARDOWN);
+        return true;
+    }
+
+    /**
+     * Re-enable connectivity to a network after a {@link #teardown()}.
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean reconnect() {
+        sendMessage(CM_CMD_RECONNECT);
+        return true;
+    }
+
+    /**
+     * Turn the wireless radio off for a network.
+     * @param turnOn {@code true} to turn the radio on, {@code false}
+     * TODO: do away with return value after making MobileDataStateTracker async
+     */
+    public boolean setRadio(boolean turnOn) {
+        setWifiEnabled(turnOn);
+        return true;
     }
 
     /**
@@ -538,753 +588,893 @@
      * unavailable.
      * @return {@code true} if Wi-Fi connections are possible
      */
-    public synchronized boolean isAvailable() {
-        /*
-         * TODO: Need to also look at scan results to see whether we're
-         * in range of any access points. If we have scan results that
-         * are no more than N seconds old, use those, otherwise, initiate
-         * a scan and wait for the results. This only matters if we
-         * allow mobile to be the preferred network.
-         */
-        SupplicantState suppState = mWifiInfo.getSupplicantState();
-        return suppState != SupplicantState.UNINITIALIZED &&
-                suppState != SupplicantState.INACTIVE &&
-                (mTornDownByConnMgr || !isDriverStopped());
+    public boolean isAvailable() {
+        return mNetworkInfo.isAvailable();
     }
 
     /**
-     * {@inheritDoc}
-     * There are currently no defined Wi-Fi subtypes.
+     * Tells the underlying networking system that the caller wants to
+     * begin using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature to be used
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     * TODO: needs to go away
      */
-    public int getNetworkSubtype() {
-        return 0;
+    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        return -1;
     }
 
     /**
-     * Helper method: updates the network info object to keep it in sync with
-     * the Wi-Fi state tracker.
+     * Tells the underlying networking system that the caller is finished
+     * using the named feature. The interpretation of {@code feature}
+     * is completely up to each networking implementation.
+     * @param feature the name of the feature that is no longer needed.
+     * @param callingPid the process ID of the process that is issuing this request
+     * @param callingUid the user ID of the process that is issuing this request
+     * @return an integer value representing the outcome of the request.
+     * The interpretation of this value is specific to each networking
+     * implementation+feature combination, except that the value {@code -1}
+     * always indicates failure.
+     * TODO: needs to go away
      */
-    private void updateNetworkInfo() {
-        mNetworkInfo.setIsAvailable(isAvailable());
+    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
+        return -1;
+    }
+
+    /* TODO: will go away.
+     * Notifications are directly handled in WifiStateTracker at checkAndSetNotification()
+     */
+    public void interpretScanResultsAvailable() {
+
     }
 
     /**
-     * Report whether the Wi-Fi connection is fully configured for data.
-     * @return {@code true} if the {@link SupplicantState} is
-     * {@link android.net.wifi.SupplicantState#COMPLETED COMPLETED}.
+     * Return the name of our WLAN network interface.
+     * @return the name of our interface.
      */
-    public boolean isConnectionCompleted() {
-        return mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED;
+    public String getInterfaceName() {
+        return mInterfaceName;
     }
 
     /**
-     * Report whether the Wi-Fi connection has successfully acquired an IP address.
-     * @return {@code true} if the Wi-Fi connection has been assigned an IP address.
+     * Check if private DNS route is set for the network
      */
-    public boolean hasIpAddress() {
-        return mHaveIpAddress;
+    public boolean isPrivateDnsRouteSet() {
+        return mPrivateDnsRouteSet.get();
     }
 
     /**
-     * Send the tracker a notification that a user-entered password key
-     * may be incorrect (i.e., caused authentication to fail).
+     * Set a flag indicating private DNS route is set
      */
-    void notifyPasswordKeyMayBeIncorrect() {
-        sendEmptyMessage(EVENT_PASSWORD_KEY_MAY_BE_INCORRECT);
+    public void privateDnsRouteSet(boolean enabled) {
+        mPrivateDnsRouteSet.set(enabled);
     }
 
     /**
-     * Send the tracker a notification that a connection to the supplicant
-     * daemon has been established.
+     * Fetch NetworkInfo for the network
      */
-    void notifySupplicantConnection() {
-        sendEmptyMessage(EVENT_SUPPLICANT_CONNECTION);
+    public NetworkInfo getNetworkInfo() {
+        return mNetworkInfo;
     }
 
     /**
-     * Send the tracker a notification that the state of the supplicant
-     * has changed.
-     * @param networkId the configured network on which the state change occurred
-     * @param newState the new {@code SupplicantState}
+     * Fetch NetworkProperties for the network
      */
-    void notifyStateChange(int networkId, String BSSID, SupplicantState newState) {
-        Message msg = Message.obtain(
-            this, EVENT_SUPPLICANT_STATE_CHANGED,
-            new SupplicantStateChangeResult(networkId, BSSID, newState));
-        msg.sendToTarget();
+    public NetworkProperties getNetworkProperties() {
+        return mNetworkProperties;
     }
 
     /**
-     * Send the tracker a notification that the state of Wifi connectivity
-     * has changed.
-     * @param networkId the configured network on which the state change occurred
-     * @param newState the new network state
-     * @param BSSID when the new state is {@link DetailedState#CONNECTED
-     * NetworkInfo.DetailedState.CONNECTED},
-     * this is the MAC address of the access point. Otherwise, it
-     * is {@code null}.
+     * Fetch default gateway address for the network
      */
-    void notifyStateChange(DetailedState newState, String BSSID, int networkId) {
-        Message msg = Message.obtain(
-            this, EVENT_NETWORK_STATE_CHANGED,
-            new NetworkStateChangeResult(newState, BSSID, networkId));
-        msg.sendToTarget();
+    public int getDefaultGatewayAddr() {
+        return mDefaultGatewayAddr.get();
     }
 
     /**
-     * Send the tracker a notification that a scan has completed, and results
-     * are available.
+     * Check if default route is set
      */
-    void notifyScanResultsAvailable() {
-        // reset the supplicant's handling of scan results to "normal" mode
-        setScanResultHandling(SUPPL_SCAN_HANDLING_NORMAL);
-        sendEmptyMessage(EVENT_SCAN_RESULTS_AVAILABLE);
+    public boolean isDefaultRouteSet() {
+        return mDefaultRouteSet.get();
     }
 
     /**
-     * Send the tracker a notification that we can no longer communicate with
-     * the supplicant daemon.
+     * Set a flag indicating default route is set for the network
      */
-    void notifySupplicantLost() {
-        sendEmptyMessage(EVENT_SUPPLICANT_DISCONNECT);
+    public void defaultRouteSet(boolean enabled) {
+        mDefaultRouteSet.set(enabled);
     }
 
     /**
-     * Send the tracker a notification that the Wi-Fi driver has been stopped.
+     * Return the system properties name associated with the tcp buffer sizes
+     * for this network.
      */
-    void notifyDriverStopped() {
-        mRunState = RUN_STATE_STOPPED;
+    public String getTcpBufferSizesPropName() {
+        return "net.tcp.buffersize.wifi";
+    }
 
-        // Send a driver stopped message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STOPPED, 0).sendToTarget();
+
+    /*********************************************************
+     * Methods exposed for public use
+     ********************************************************/
+
+    /**
+     * TODO: doc
+     */
+    public boolean pingSupplicant() {
+        return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue;
     }
 
     /**
-     * Send the tracker a notification that the Wi-Fi driver has been restarted after
-     * having been stopped.
+     * TODO: doc
      */
-    void notifyDriverStarted() {
-        // Send a driver started message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_STARTED, 0).sendToTarget();
+    public boolean startScan(boolean forceActive) {
+        return sendSyncMessage(obtainMessage(CMD_START_SCAN, forceActive ?
+                SCAN_ACTIVE : SCAN_PASSIVE, 0)).boolValue;
     }
 
     /**
-     * Send the tracker a notification that the Wi-Fi driver has hung and needs restarting.
+     * TODO: doc
      */
-    void notifyDriverHung() {
-        // Send a driver hanged message to our handler
-        Message.obtain(this, EVENT_DRIVER_STATE_CHANGED, DRIVER_HUNG, 0).sendToTarget();
-    }
-
-    /**
-     * Set the interval timer for polling connection information
-     * that is not delivered asynchronously.
-     */
-    private synchronized void checkPollTimer() {
-        if (mEnableRssiPolling &&
-                mWifiInfo.getSupplicantState() == SupplicantState.COMPLETED &&
-                !hasMessages(EVENT_POLL_INTERVAL)) {
-            sendEmptyMessageDelayed(EVENT_POLL_INTERVAL, POLL_STATUS_INTERVAL_MSECS);
+    public void setWifiEnabled(boolean enable) {
+        mLastEnableUid.set(Binder.getCallingUid());
+        if (enable) {
+            /* Argument is the state that is entered prior to load */
+            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
+            sendMessage(CMD_START_SUPPLICANT);
+        } else {
+            sendMessage(CMD_STOP_SUPPLICANT);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
         }
     }
 
     /**
-     * TODO: mRunState is not synchronized in some places
-     * address this as part of re-architect.
+     * TODO: doc
+     */
+    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
+        mLastApEnableUid.set(Binder.getCallingUid());
+        if (enable) {
+            /* Argument is the state that is entered prior to load */
+            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0));
+            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
+        } else {
+            sendMessage(CMD_STOP_AP);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0));
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public int getWifiState() {
+        return mWifiState.get();
+    }
+
+    /**
+     * TODO: doc
+     */
+    public String getWifiStateByName() {
+        switch (mWifiState.get()) {
+            case WIFI_STATE_DISABLING:
+                return "disabling";
+            case WIFI_STATE_DISABLED:
+                return "disabled";
+            case WIFI_STATE_ENABLING:
+                return "enabling";
+            case WIFI_STATE_ENABLED:
+                return "enabled";
+            case WIFI_STATE_UNKNOWN:
+                return "unknown state";
+            default:
+                return "[invalid state]";
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public int getWifiApState() {
+        return mWifiApState.get();
+    }
+
+    /**
+     * TODO: doc
+     */
+    public String getWifiApStateByName() {
+        switch (mWifiApState.get()) {
+            case WIFI_AP_STATE_DISABLING:
+                return "disabling";
+            case WIFI_AP_STATE_DISABLED:
+                return "disabled";
+            case WIFI_AP_STATE_ENABLING:
+                return "enabling";
+            case WIFI_AP_STATE_ENABLED:
+                return "enabled";
+            case WIFI_AP_STATE_FAILED:
+                return "failed";
+            default:
+                return "[invalid state]";
+        }
+    }
+
+    /**
+     * Get status information for the current connection, if any.
+     * @return a {@link WifiInfo} object containing information about the current connection
      *
-     * TODO: We are exposing an additional public synchronized call
-     * for a wakelock optimization in WifiService. Remove it
-     * when we handle the wakelock in ConnectivityService.
      */
-    public synchronized boolean isDriverStopped() {
-        return mRunState == RUN_STATE_STOPPED || mRunState == RUN_STATE_STOPPING;
+    public WifiInfo requestConnectionInfo() {
+        return mWifiInfo;
     }
 
-    private void noteRunState() {
+    public DhcpInfo getDhcpInfo() {
+        return mDhcpInfo;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void startWifi(boolean enable) {
+      if (enable) {
+          sendMessage(CMD_START_DRIVER);
+      } else {
+          sendMessage(CMD_STOP_DRIVER);
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void disconnectAndStop() {
+        sendMessage(CMD_DISCONNECT);
+        sendMessage(CMD_STOP_DRIVER);
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setScanOnlyMode(boolean enable) {
+      if (enable) {
+          sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0));
+      } else {
+          sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0));
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setScanType(boolean active) {
+      if (active) {
+          sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_ACTIVE, 0));
+      } else {
+          sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_PASSIVE, 0));
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public List<ScanResult> getScanResultsList() {
+        return mScanResults;
+    }
+
+    /**
+     * Disconnect from Access Point
+     */
+    public boolean disconnectCommand() {
+        return sendSyncMessage(CMD_DISCONNECT).boolValue;
+    }
+
+    /**
+     * Initiate a reconnection to AP
+     */
+    public boolean reconnectCommand() {
+        return sendSyncMessage(CMD_RECONNECT).boolValue;
+    }
+
+    /**
+     * Initiate a re-association to AP
+     */
+    public boolean reassociateCommand() {
+        return sendSyncMessage(CMD_REASSOCIATE).boolValue;
+    }
+
+    /**
+     * Add a network synchronously
+     *
+     * @return network id of the new network
+     */
+    public int addOrUpdateNetwork(WifiConfiguration config) {
+        return sendSyncMessage(CMD_ADD_OR_UPDATE_NETWORK, config).intValue;
+    }
+
+    public List<WifiConfiguration> getConfiguredNetworks() {
+        return sendSyncMessage(CMD_GET_NETWORK_CONFIG).configList;
+    }
+
+    /**
+     * Delete a network
+     *
+     * @param networkId id of the network to be removed
+     */
+    public boolean removeNetwork(int networkId) {
+        return sendSyncMessage(obtainMessage(CMD_REMOVE_NETWORK, networkId, 0)).boolValue;
+    }
+
+    private class EnableNetParams {
+        private int netId;
+        private boolean disableOthers;
+        EnableNetParams(int n, boolean b) {
+            netId = n;
+            disableOthers = b;
+        }
+    }
+    /**
+     * Enable a network
+     *
+     * @param netId network id of the network
+     * @param disableOthers true, if all other networks have to be disabled
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     */
+    public boolean enableNetwork(int netId, boolean disableOthers) {
+        return sendSyncMessage(CMD_ENABLE_NETWORK,
+                new EnableNetParams(netId, disableOthers)).boolValue;
+    }
+
+    /**
+     * Disable a network
+     *
+     * @param netId network id of the network
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     */
+    public boolean disableNetwork(int netId) {
+        return sendSyncMessage(obtainMessage(CMD_DISABLE_NETWORK, netId, 0)).boolValue;
+    }
+
+    /**
+     * Blacklist a BSSID. This will avoid the AP if there are
+     * alternate APs to connect
+     *
+     * @param bssid BSSID of the network
+     */
+    public void addToBlacklist(String bssid) {
+        sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid));
+    }
+
+    /**
+     * Clear the blacklist list
+     *
+     */
+    public void clearBlacklist() {
+        sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST));
+    }
+
+    /**
+     * Get detailed status of the connection
+     *
+     * @return Example status result
+     *  bssid=aa:bb:cc:dd:ee:ff
+     *  ssid=TestNet
+     *  id=3
+     *  pairwise_cipher=NONE
+     *  group_cipher=NONE
+     *  key_mgmt=NONE
+     *  wpa_state=COMPLETED
+     *  ip_address=X.X.X.X
+     */
+    public String status() {
+        return sendSyncMessage(CMD_CONNECTION_STATUS).stringValue;
+    }
+
+    public void enableRssiPolling(boolean enabled) {
+       sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0));
+    }
+    /**
+     * Get RSSI to currently connected network
+     *
+     * @return RSSI value, -1 on failure
+     */
+    public int getRssi() {
+        return sendSyncMessage(CMD_GET_RSSI).intValue;
+    }
+
+    /**
+     * Get approx RSSI to currently connected network
+     *
+     * @return RSSI value, -1 on failure
+     */
+    public int getRssiApprox() {
+        return sendSyncMessage(CMD_GET_RSSI_APPROX).intValue;
+    }
+
+    /**
+     * Get link speed to currently connected network
+     *
+     * @return link speed, -1 on failure
+     */
+    public int getLinkSpeed() {
+        return sendSyncMessage(CMD_GET_LINK_SPEED).intValue;
+    }
+
+    /**
+     * Get MAC address of radio
+     *
+     * @return MAC address, null on failure
+     */
+    public String getMacAddress() {
+        return sendSyncMessage(CMD_GET_MAC_ADDR).stringValue;
+    }
+
+    /**
+     * Start packet filtering
+     */
+    public void startPacketFiltering() {
+        sendMessage(CMD_START_PACKET_FILTERING);
+    }
+
+    /**
+     * Stop packet filtering
+     */
+    public void stopPacketFiltering() {
+        sendMessage(CMD_STOP_PACKET_FILTERING);
+    }
+
+    /**
+     * Set power mode
+     * @param mode
+     *     DRIVER_POWER_MODE_AUTO
+     *     DRIVER_POWER_MODE_ACTIVE
+     */
+    public void setPowerMode(int mode) {
+        sendMessage(obtainMessage(CMD_SET_POWER_MODE, mode, 0));
+    }
+
+    /**
+     * Set the number of allowed radio frequency channels from the system
+     * setting value, if any.
+     */
+    public void setNumAllowedChannels() {
         try {
-            if (mRunState == RUN_STATE_RUNNING) {
-                mBatteryStats.noteWifiRunning();
-            } else if (mRunState == RUN_STATE_STOPPED) {
-                mBatteryStats.noteWifiStopped();
+            setNumAllowedChannels(
+                    Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
+        } catch (Settings.SettingNotFoundException e) {
+            if (mNumAllowedChannels != 0) {
+                setNumAllowedChannels(mNumAllowedChannels);
             }
-        } catch (RemoteException ignore) {
+            // otherwise, use the driver default
         }
     }
 
     /**
-     * Set the run state to either "normal" or "scan-only".
-     * @param scanOnlyMode true if the new mode should be scan-only.
+     * Set the number of radio frequency channels that are allowed to be used
+     * in the current regulatory domain.
+     * @param numChannels the number of allowed channels. Must be greater than 0
+     * and less than or equal to 16.
      */
-    public synchronized void setScanOnlyMode(boolean scanOnlyMode) {
-        // do nothing unless scan-only mode is changing
-        if (mIsScanOnly != scanOnlyMode) {
-            int scanType = (scanOnlyMode ?
-                    SUPPL_SCAN_HANDLING_LIST_ONLY : SUPPL_SCAN_HANDLING_NORMAL);
-            if (LOCAL_LOGD) Log.v(TAG, "Scan-only mode changing to " + scanOnlyMode + " scanType=" + scanType);
-            if (setScanResultHandling(scanType)) {
-                mIsScanOnly = scanOnlyMode;
-                if (!isDriverStopped()) {
-                    if (scanOnlyMode) {
-                        disconnect();
-                    } else {
-                        reconnectCommand();
-                    }
-                }
-            }
-        }
+    public void setNumAllowedChannels(int numChannels) {
+        sendMessage(obtainMessage(CMD_SET_NUM_ALLOWED_CHANNELS, numChannels, 0));
     }
 
-
-    private void checkIsBluetoothPlaying() {
-        boolean isBluetoothPlaying = false;
-        Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks();
-
-        for (BluetoothDevice device : connected) {
-            if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) {
-                isBluetoothPlaying = true;
-                break;
-            }
-        }
-        setBluetoothScanMode(isBluetoothPlaying);
+    /**
+     * Get number of allowed channels
+     *
+     * @return channel count, -1 on failure
+     *
+     * TODO: this is not a public API and needs to be removed in favor
+     * of asynchronous reporting. unused for now.
+     */
+    public int getNumAllowedChannels() {
+        return -1;
     }
 
-    public void enableRssiPolling(boolean enable) {
-        if (mEnableRssiPolling != enable) {
-            mEnableRssiPolling = enable;
-            checkPollTimer();
+    /**
+     * Set bluetooth coex mode:
+     *
+     * @param mode
+     *  BLUETOOTH_COEXISTENCE_MODE_ENABLED
+     *  BLUETOOTH_COEXISTENCE_MODE_DISABLED
+     *  BLUETOOTH_COEXISTENCE_MODE_SENSE
+     */
+    public void setBluetoothCoexistenceMode(int mode) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0));
+    }
+
+    /**
+     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
+     * some of the low-level scan parameters used by the driver are changed to
+     * reduce interference with A2DP streaming.
+     *
+     * @param isBluetoothPlaying whether to enable or disable this mode
+     */
+    public void setBluetoothScanMode(boolean isBluetoothPlaying) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_SCAN_MODE, isBluetoothPlaying ? 1 : 0, 0));
+    }
+
+    /**
+     * Save configuration on supplicant
+     *
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     *
+     * TODO: deprecate this
+     */
+    public boolean saveConfig() {
+        return sendSyncMessage(CMD_SAVE_CONFIG).boolValue;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void requestCmWakeLock() {
+        sendMessage(CMD_REQUEST_CM_WAKELOCK);
+    }
+
+    /*********************************************************
+     * Internal private functions
+     ********************************************************/
+
+    class SyncReturn {
+        boolean boolValue;
+        int intValue;
+        String stringValue;
+        Object objValue;
+        List<WifiConfiguration> configList;
+    }
+
+    class SyncParams {
+        Object mParameter;
+        SyncReturn mSyncReturn;
+        SyncParams() {
+            mSyncReturn = new SyncReturn();
+        }
+        SyncParams(Object p) {
+            mParameter = p;
+            mSyncReturn = new SyncReturn();
         }
     }
 
     /**
-     * Tracks the WPA supplicant states to detect "loop" situations.
-     * @param newSupplicantState The new WPA supplicant state.
-     * @return {@code true} if the supplicant loop should be stopped
-     * and {@code false} if it should continue.
+     * message.arg2 is reserved to indicate synchronized
+     * message.obj is used to store SyncParams
      */
-    private boolean isSupplicantLooping(SupplicantState newSupplicantState) {
-        if (SupplicantState.ASSOCIATING.ordinal() <= newSupplicantState.ordinal()
-            && newSupplicantState.ordinal() < SupplicantState.COMPLETED.ordinal()) {
-            if (mSupplicantLoopState != newSupplicantState) {
-                if (newSupplicantState.ordinal() < mSupplicantLoopState.ordinal()) {
-                    ++mNumSupplicantLoopIterations;
-                }
-
-                mSupplicantLoopState = newSupplicantState;
+    private SyncReturn syncedSend(Message msg) {
+        SyncParams syncParams = (SyncParams) msg.obj;
+        msg.arg2 = SYNCHRONOUS_CALL;
+        synchronized(syncParams) {
+            if (DBG) Log.d(TAG, "syncedSend " + msg);
+            sendMessage(msg);
+            try {
+                syncParams.wait();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "sendSyncMessage: unexpected interruption of wait()");
+                return null;
             }
-        } else if (newSupplicantState == SupplicantState.COMPLETED) {
-            resetSupplicantLoopState();
+        }
+        return syncParams.mSyncReturn;
+    }
+
+    private SyncReturn sendSyncMessage(Message msg) {
+        SyncParams syncParams = new SyncParams();
+        msg.obj = syncParams;
+        return syncedSend(msg);
+    }
+
+    private SyncReturn sendSyncMessage(int what, Object param) {
+        SyncParams syncParams = new SyncParams(param);
+        Message msg = obtainMessage(what, syncParams);
+        return syncedSend(msg);
+    }
+
+
+    private SyncReturn sendSyncMessage(int what) {
+        return sendSyncMessage(obtainMessage(what));
+    }
+
+    private void notifyOnMsgObject(Message msg) {
+        SyncParams syncParams = (SyncParams) msg.obj;
+        if (syncParams != null) {
+            synchronized(syncParams) {
+                if (DBG) Log.d(TAG, "notifyOnMsgObject " + msg);
+                syncParams.notify();
+            }
+        }
+        else {
+            Log.e(TAG, "Error! syncParams in notifyOnMsgObject is null");
+        }
+    }
+
+    private void setWifiState(int wifiState) {
+        final int previousWifiState = mWifiState.get();
+
+        try {
+            if (wifiState == WIFI_STATE_ENABLED) {
+                mBatteryStats.noteWifiOn(mLastEnableUid.get());
+            } else if (wifiState == WIFI_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff(mLastEnableUid.get());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to note battery stats in wifi");
         }
 
-        return mNumSupplicantLoopIterations >= MAX_SUPPLICANT_LOOP_ITERATIONS;
+        mWifiState.set(wifiState);
+
+        if (DBG) Log.d(TAG, "setWifiState: " + getWifiStateByName());
+
+        final Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_STATE, wifiState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, previousWifiState);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void setWifiApState(int wifiApState) {
+        final int previousWifiApState = mWifiApState.get();
+
+        try {
+            if (wifiApState == WIFI_AP_STATE_ENABLED) {
+                mBatteryStats.noteWifiOn(mLastApEnableUid.get());
+            } else if (wifiApState == WIFI_AP_STATE_DISABLED) {
+                mBatteryStats.noteWifiOff(mLastApEnableUid.get());
+            }
+        } catch (RemoteException e) {
+            Log.d(TAG, "Failed to note battery stats in wifi");
+        }
+
+        // Update state
+        mWifiApState.set(wifiApState);
+
+        if (DBG) Log.d(TAG, "setWifiApState: " + getWifiApStateByName());
+
+        // Broadcast
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
+        mContext.sendStickyBroadcast(intent);
     }
 
     /**
-     * Resets the WPA supplicant loop state.
+     * Parse the scan result line passed to us by wpa_supplicant (helper).
+     * @param line the line to parse
+     * @return the {@link ScanResult} object
      */
-    private void resetSupplicantLoopState() {
-        mNumSupplicantLoopIterations = 0;
-    }
-
-    @Override
-    public void handleMessage(Message msg) {
-        Intent intent;
-
-        switch (msg.what) {
-            case EVENT_SUPPLICANT_CONNECTION:
-                mRunState = RUN_STATE_RUNNING;
-                noteRunState();
-                checkUseStaticIp();
-                /* Reset notification state on new connection */
-                resetNotificationTimer();
-                /*
-                 * DHCP requests are blocking, so run them in a separate thread.
-                 */
-                HandlerThread dhcpThread = new HandlerThread("DHCP Handler Thread");
-                dhcpThread.start();
-                mDhcpTarget = new DhcpHandler(dhcpThread.getLooper(), this);
-                mIsScanModeActive = true;
-                mTornDownByConnMgr = false;
-                mLastBssid = null;
-                mLastSsid = null;
-                requestConnectionInfo();
-                SupplicantState supplState = mWifiInfo.getSupplicantState();
-                /**
-                 * The MAC address isn't going to change, so just request it
-                 * once here.
-                 */
-                String macaddr = getMacAddress();
-
-                if (macaddr != null) {
-                    mWifiInfo.setMacAddress(macaddr);
-                }
-                if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant established, state=" +
-                    supplState);
-                // Wi-Fi supplicant connection state changed:
-                // [31- 2] Reserved for future use
-                // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
-                //         or supplicant died (2)
-                EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, 1);
-                /*
-                 * The COMPLETED state change from the supplicant may have occurred
-                 * in between polling for supplicant availability, in which case
-                 * we didn't perform a DHCP request to get an IP address.
-                 */
-                if (supplState == SupplicantState.COMPLETED) {
-                    mLastBssid = mWifiInfo.getBSSID();
-                    mLastSsid = mWifiInfo.getSSID();
-                    configureInterface();
-                }
-                if (ActivityManagerNative.isSystemReady()) {
-                    intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
-                    intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, true);
-                    mContext.sendBroadcast(intent);
-                }
-                if (supplState == SupplicantState.COMPLETED && mHaveIpAddress) {
-                    setDetailedState(DetailedState.CONNECTED);
-                } else {
-                    setDetailedState(WifiInfo.getDetailedStateOf(supplState));
-                }
-                /*
-                 * Filter out multicast packets. This saves battery power, since
-                 * the CPU doesn't have to spend time processing packets that
-                 * are going to end up being thrown away.
-                 */
-                mWM.initializeMulticastFiltering();
-
-                if (mBluetoothA2dp == null) {
-                    mBluetoothA2dp = new BluetoothA2dp(mContext);
-                }
-                checkIsBluetoothPlaying();
-
-                // initialize this after the supplicant is alive
-                setNumAllowedChannels();
-                break;
-
-            case EVENT_SUPPLICANT_DISCONNECT:
-                mRunState = RUN_STATE_STOPPED;
-                noteRunState();
-                boolean died = mWifiState.get() != WIFI_STATE_DISABLED &&
-                               mWifiState.get() != WIFI_STATE_DISABLING;
-                if (died) {
-                    if (LOCAL_LOGD) Log.v(TAG, "Supplicant died unexpectedly");
-                } else {
-                    if (LOCAL_LOGD) Log.v(TAG, "Connection to supplicant lost");
-                }
-                // Wi-Fi supplicant connection state changed:
-                // [31- 2] Reserved for future use
-                // [ 1- 0] Connected to supplicant (1), disconnected from supplicant (0) ,
-                //         or supplicant died (2)
-                EventLog.writeEvent(EVENTLOG_SUPPLICANT_CONNECTION_STATE_CHANGED, died ? 2 : 0);
-                closeSupplicantConnection();
-
-                if (died) {
-                    resetConnections(true);
-                }
-                // When supplicant dies, kill the DHCP thread
-                if (mDhcpTarget != null) {
-                    mDhcpTarget.getLooper().quit();
-                    mDhcpTarget = null;
-                }
-                mContext.removeStickyBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION));
-                if (ActivityManagerNative.isSystemReady()) {
-                    intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
-                    intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
-                    mContext.sendBroadcast(intent);
-                }
-                setDetailedState(DetailedState.DISCONNECTED);
-                setSupplicantState(SupplicantState.UNINITIALIZED);
-                mNetworkProperties.clear();
-                mHaveIpAddress = false;
-                mObtainingIpAddress = false;
-                if (died) {
-                    mWM.setWifiEnabled(false);
-                }
-                break;
-
-            case EVENT_MAYBE_START_SCAN_POST_DISCONNECT:
-                // Only do this if we haven't gotten a new supplicant status since the timer
-                // started
-                if (mNumSupplicantStateChanges == msg.arg1) {
-                    scan(false); // do a passive scan
-                }
-                break;
-
-            case EVENT_SUPPLICANT_STATE_CHANGED:
-                mNumSupplicantStateChanges++;
-                SupplicantStateChangeResult supplicantStateResult =
-                    (SupplicantStateChangeResult) msg.obj;
-                SupplicantState newState = supplicantStateResult.state;
-                SupplicantState currentState = mWifiInfo.getSupplicantState();
-
-                // Wi-Fi supplicant state changed:
-                // [31- 6] Reserved for future use
-                // [ 5- 0] Supplicant state ordinal (as defined by SupplicantState)
-                int eventLogParam = (newState.ordinal() & 0x3f);
-                EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, eventLogParam);
-
-                if (LOCAL_LOGD) Log.v(TAG, "Changing supplicant state: "
-                                      + currentState +
-                                      " ==> " + newState);
-
-                int networkId = supplicantStateResult.networkId;
-
-                /**
-                 * The SupplicantState BSSID value is valid in ASSOCIATING state only.
-                 * The NetworkState BSSID value comes upon a successful connection.
-                 */
-                if (supplicantStateResult.state == SupplicantState.ASSOCIATING) {
-                    mLastBssid = supplicantStateResult.BSSID;
-                }
-                /*
-                 * If we get disconnect or inactive we need to start our
-                 * watchdog timer to start a scan
-                 */
-                if (newState == SupplicantState.DISCONNECTED ||
-                        newState == SupplicantState.INACTIVE) {
-                    sendMessageDelayed(obtainMessage(EVENT_MAYBE_START_SCAN_POST_DISCONNECT,
-                            mNumSupplicantStateChanges, 0), KICKSTART_SCANNING_DELAY_MSECS);
-                }
-
-
-                /*
-                 * Did we get to DISCONNECTED state due to an
-                 * authentication (password) failure?
-                 */
-                boolean failedToAuthenticate = false;
-                if (newState == SupplicantState.DISCONNECTED) {
-                    failedToAuthenticate = mPasswordKeyMayBeIncorrect;
-                }
-                mPasswordKeyMayBeIncorrect = false;
-
-                /*
-                 * Keep track of the supplicant state and check if we should
-                 * disable the network
-                 */
-                boolean disabledNetwork = false;
-                if (isSupplicantLooping(newState)) {
-                    if (LOCAL_LOGD) {
-                        Log.v(TAG,
-                              "Stop WPA supplicant loop and disable network");
-                    }
-                    disabledNetwork = wifiManagerDisableNetwork(networkId);
-                }
-
-                if (disabledNetwork) {
-                    /*
-                     * Reset the loop state if we disabled the network
-                     */
-                    resetSupplicantLoopState();
-                } else if (newState != currentState ||
-                        (newState == SupplicantState.DISCONNECTED && isDriverStopped())) {
-                    setSupplicantState(newState);
-                    if (newState == SupplicantState.DORMANT) {
-                        DetailedState newDetailedState;
-                        Message reconnectMsg = obtainMessage(EVENT_DEFERRED_RECONNECT, mLastBssid);
-                        if (mIsScanOnly || mRunState == RUN_STATE_STOPPING) {
-                            newDetailedState = DetailedState.IDLE;
-                        } else {
-                            newDetailedState = DetailedState.FAILED;
-                        }
-                        handleDisconnectedState(newDetailedState, true);
-                        /**
-                         * If we were associated with a network (networkId != -1),
-                         * assume we reached this state because of a failed attempt
-                         * to acquire an IP address, and attempt another connection
-                         * and IP address acquisition in RECONNECT_DELAY_MSECS
-                         * milliseconds.
+    private ScanResult parseScanResult(String line) {
+        ScanResult scanResult = null;
+        if (line != null) {
+            /*
+             * Cache implementation (LinkedHashMap) is not synchronized, thus,
+             * must synchronized here!
+             */
+            synchronized (mScanResultCache) {
+                String[] result = scanResultPattern.split(line);
+                if (3 <= result.length && result.length <= 5) {
+                    String bssid = result[0];
+                    // bssid | frequency | level | flags | ssid
+                    int frequency;
+                    int level;
+                    try {
+                        frequency = Integer.parseInt(result[1]);
+                        level = Integer.parseInt(result[2]);
+                        /* some implementations avoid negative values by adding 256
+                         * so we need to adjust for that here.
                          */
-                        if (mRunState == RUN_STATE_RUNNING && !mIsScanOnly && networkId != -1) {
-                            sendMessageDelayed(reconnectMsg, RECONNECT_DELAY_MSECS);
-                        } else if (mRunState == RUN_STATE_STOPPING) {
-                            stopDriver();
-                        } else if (mRunState == RUN_STATE_STARTING && !mIsScanOnly) {
-                            reconnectCommand();
-                        }
-                    } else if (newState == SupplicantState.DISCONNECTED) {
-                        mNetworkProperties.clear();
-                        mHaveIpAddress = false;
-                        if (isDriverStopped() || mDisconnectExpected) {
-                            handleDisconnectedState(DetailedState.DISCONNECTED, true);
+                        if (level > 0) level -= 256;
+                    } catch (NumberFormatException e) {
+                        frequency = 0;
+                        level = 0;
+                    }
+
+                    /*
+                     * The formatting of the results returned by
+                     * wpa_supplicant is intended to make the fields
+                     * line up nicely when printed,
+                     * not to make them easy to parse. So we have to
+                     * apply some heuristics to figure out which field
+                     * is the SSID and which field is the flags.
+                     */
+                    String ssid;
+                    String flags;
+                    if (result.length == 4) {
+                        if (result[3].charAt(0) == '[') {
+                            flags = result[3];
+                            ssid = "";
                         } else {
-                            scheduleDisconnect();
+                            flags = "";
+                            ssid = result[3];
                         }
-                    } else if (newState != SupplicantState.COMPLETED && !mDisconnectPending) {
-                        /**
-                         * Ignore events that don't change the connectivity state,
-                         * such as WPA rekeying operations.
-                         */
-                        if (!(currentState == SupplicantState.COMPLETED &&
-                               (newState == SupplicantState.ASSOCIATING ||
-                                newState == SupplicantState.ASSOCIATED ||
-                                newState == SupplicantState.FOUR_WAY_HANDSHAKE ||
-                                newState == SupplicantState.GROUP_HANDSHAKE))) {
-                            setDetailedState(WifiInfo.getDetailedStateOf(newState));
-                        }
-                    }
-
-                    mDisconnectExpected = false;
-                    intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                            | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-                    intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)newState);
-                    if (failedToAuthenticate) {
-                        if (LOCAL_LOGD) Log.d(TAG, "Failed to authenticate, disabling network " + networkId);
-                        wifiManagerDisableNetwork(networkId);
-                        intent.putExtra(
-                            WifiManager.EXTRA_SUPPLICANT_ERROR,
-                            WifiManager.ERROR_AUTHENTICATING);
-                    }
-                    mContext.sendStickyBroadcast(intent);
-                }
-                break;
-
-            case EVENT_NETWORK_STATE_CHANGED:
-                /*
-                 * Each CONNECT or DISCONNECT generates a pair of events.
-                 * One is a supplicant state change event, and the other
-                 * is a network state change event. For connects, the
-                 * supplicant event always arrives first, followed by
-                 * the network state change event. Only the latter event
-                 * has the BSSID, which we are interested in capturing.
-                 * For disconnects, the order is the opposite -- the
-                 * network state change event comes first, followed by
-                 * the supplicant state change event.
-                 */
-                NetworkStateChangeResult result =
-                    (NetworkStateChangeResult) msg.obj;
-
-                // Wi-Fi network state changed:
-                // [31- 6] Reserved for future use
-                // [ 5- 0] Detailed state ordinal (as defined by NetworkInfo.DetailedState)   
-                eventLogParam = (result.state.ordinal() & 0x3f);
-                EventLog.writeEvent(EVENTLOG_NETWORK_STATE_CHANGED, eventLogParam);
-                
-                if (LOCAL_LOGD) Log.v(TAG, "New network state is " + result.state);
-                /*
-                 * If we're in scan-only mode, don't advance the state machine, and
-                 * don't report the state change to clients.
-                 */
-                if (mIsScanOnly) {
-                    if (LOCAL_LOGD) Log.v(TAG, "Dropping event in scan-only mode");
-                    break;
-                }
-                if (result.state != DetailedState.SCANNING) {
-                    /*
-                     * Reset the scan count since there was a network state
-                     * change. This could be from supplicant trying to associate
-                     * with a network.
-                     */
-                    mNumScansSinceNetworkStateChange = 0;
-                }
-                /*
-                 * If the supplicant sent us a CONNECTED event, we don't
-                 * want to send out an indication of overall network
-                 * connectivity until we have our IP address. If the
-                 * supplicant sent us a DISCONNECTED event, we delay
-                 * sending a notification in case a reconnection to
-                 * the same access point occurs within a short time.
-                 */
-                if (result.state == DetailedState.DISCONNECTED) {
-                    if (mWifiInfo.getSupplicantState() != SupplicantState.DORMANT) {
-                        scheduleDisconnect();
-                    }
-                    break;
-                }
-                requestConnectionStatus(mWifiInfo);
-                if (!(result.state == DetailedState.CONNECTED &&
-                        (!mHaveIpAddress || mDisconnectPending))) {
-                    setDetailedState(result.state);
-                }
-
-                if (result.state == DetailedState.CONNECTED) {
-                    /*
-                     * Remove the 'available networks' notification when we
-                     * successfully connect to a network.
-                     */
-                    setNotificationVisible(false, 0, false, 0);
-                    boolean wasDisconnectPending = mDisconnectPending;
-                    cancelDisconnect();
-                    /*
-                     * The connection is fully configured as far as link-level
-                     * connectivity is concerned, but we may still need to obtain
-                     * an IP address.
-                     */
-                    if (wasDisconnectPending) {
-                        DetailedState saveState = getNetworkInfo().getDetailedState();
-                        handleDisconnectedState(DetailedState.DISCONNECTED, false);
-                        setDetailedStateInternal(saveState);
-                    }
-
-                    configureInterface();
-                    mLastBssid = result.BSSID;
-                    mLastSsid = mWifiInfo.getSSID();
-                    mLastNetworkId = result.networkId;
-                    if (mHaveIpAddress) {
-                        setDetailedState(DetailedState.CONNECTED);
+                    } else if (result.length == 5) {
+                        flags = result[3];
+                        ssid = result[4];
                     } else {
-                        setDetailedState(DetailedState.OBTAINING_IPADDR);
+                        // Here, we must have 3 fields: no flags and ssid
+                        // set
+                        flags = "";
+                        ssid = "";
                     }
-                }
-                sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
-                break;
 
-            case EVENT_SCAN_RESULTS_AVAILABLE:
-                if (ActivityManagerNative.isSystemReady()) {
-                    mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
-                }
-                sendScanResultsAvailable();
-                /**
-                 * On receiving the first scan results after connecting to
-                 * the supplicant, switch scan mode over to passive.
-                 */
-                setScanMode(false);
-                break;
-
-            case EVENT_POLL_INTERVAL:
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    requestPolledInfo(mWifiInfo, true);
-                    checkPollTimer();
-                }
-                break;
-            
-            case EVENT_DEFERRED_DISCONNECT:
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    handleDisconnectedState(DetailedState.DISCONNECTED, true);
-                }
-                break;
-
-            case EVENT_DEFERRED_RECONNECT:
-                /**
-                 * mLastBssid can be null when there is a reconnect
-                 * request on the first BSSID we connect to
-                 */
-                String BSSID = (msg.obj != null) ? msg.obj.toString() : null;
-                /**
-                 * If we've exceeded the maximum number of retries for reconnecting
-                 * to a given network, disable the network
-                 */
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    if (++mReconnectCount > getMaxDhcpRetries()) {
-                        if (LOCAL_LOGD) {
-                            Log.d(TAG, "Failed reconnect count: " +
-                                    mReconnectCount + " Disabling " + BSSID);
+                    // bssid + ssid is the hash key
+                    String key = bssid + ssid;
+                    scanResult = mScanResultCache.get(key);
+                    if (scanResult != null) {
+                        scanResult.level = level;
+                        scanResult.SSID = ssid;
+                        scanResult.capabilities = flags;
+                        scanResult.frequency = frequency;
+                    } else {
+                        // Do not add scan results that have no SSID set
+                        if (0 < ssid.trim().length()) {
+                            scanResult =
+                                new ScanResult(
+                                    ssid, bssid, flags, level, frequency);
+                            mScanResultCache.put(key, scanResult);
                         }
-                        mWM.disableNetwork(mLastNetworkId);
                     }
-                    reconnectCommand();
-                }
-                break;
-
-            case EVENT_INTERFACE_CONFIGURATION_SUCCEEDED:
-                /**
-                 * Since this event is sent from another thread, it might have been
-                 * sent after we closed our connection to the supplicant in the course
-                 * of disabling Wi-Fi. In that case, we should just ignore the event.
-                 */
-                if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
-                    break;
-                }
-                mReconnectCount = 0;
-                mHaveIpAddress = true;
-                configureNetworkProperties();
-                mObtainingIpAddress = false;
-                mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
-                mLastSignalLevel = -1; // force update of signal strength
-                if (mNetworkInfo.getDetailedState() != DetailedState.CONNECTED) {
-                    setDetailedState(DetailedState.CONNECTED);
-                    sendNetworkStateChangeBroadcast(mWifiInfo.getBSSID());
                 } else {
-                    mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
+                    Log.w(TAG, "Misformatted scan result text with " +
+                          result.length + " fields: " + line);
                 }
-                if (LOCAL_LOGD) Log.v(TAG, "IP configuration: " + mDhcpInfo);
-                // Wi-Fi interface configuration state changed:
-                // [31- 1] Reserved for future use
-                // [ 0- 0] Interface configuration succeeded (1) or failed (0)   
-                EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 1);
+            }
+        }
 
-                // We've connected successfully, so allow the notification again in the future
-                resetNotificationTimer();
-                break;
+        return scanResult;
+    }
 
-            case EVENT_INTERFACE_CONFIGURATION_FAILED:
-                if (mWifiInfo.getSupplicantState() != SupplicantState.UNINITIALIZED) {
-                    // Wi-Fi interface configuration state changed:
-                    // [31- 1] Reserved for future use
-                    // [ 0- 0] Interface configuration succeeded (1) or failed (0)
-                    EventLog.writeEvent(EVENTLOG_INTERFACE_CONFIGURATION_STATE_CHANGED, 0);
-                    mNetworkProperties.clear();
-                    mHaveIpAddress = false;
-                    mWifiInfo.setIpAddress(0);
-                    mObtainingIpAddress = false;
-                    disconnect();
+    /**
+     * scanResults input format
+     * 00:bb:cc:dd:cc:ee       2427    166     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net1
+     * 00:bb:cc:dd:cc:ff       2412    165     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net2
+     */
+    private void setScanResults(String scanResults) {
+        if (scanResults == null) {
+            return;
+        }
+
+        List<ScanResult> scanList = new ArrayList<ScanResult>();
+
+        int lineCount = 0;
+
+        int scanResultsLen = scanResults.length();
+        // Parse the result string, keeping in mind that the last line does
+        // not end with a newline.
+        for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) {
+            if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') {
+                ++lineCount;
+
+                if (lineCount == 1) {
+                    lineBeg = lineEnd + 1;
+                    continue;
                 }
-                break;
-
-            case EVENT_DRIVER_STATE_CHANGED:
-                // Wi-Fi driver state changed:
-                // 0 STARTED
-                // 1 STOPPED
-                // 2 HUNG
-                EventLog.writeEvent(EVENTLOG_DRIVER_STATE_CHANGED, msg.arg1);
-
-                switch (msg.arg1) {
-                case DRIVER_STARTED:
-                    /**
-                     * Set the number of allowed radio channels according
-                     * to the system setting, since it gets reset by the
-                     * driver upon changing to the STARTED state.
-                     */
-                    setNumAllowedChannels();
-                    synchronized (this) {
-                        if (mRunState == RUN_STATE_STARTING) {
-                            mRunState = RUN_STATE_RUNNING;
-                            if (!mIsScanOnly) {
-                                reconnectCommand();
-                            } else {
-                                // In some situations, supplicant needs to be kickstarted to
-                                // start the background scanning
-                                scan(true);
-                            }
-                        }
+                if (lineEnd > lineBeg) {
+                    String line = scanResults.substring(lineBeg, lineEnd);
+                    ScanResult scanResult = parseScanResult(line);
+                    if (scanResult != null) {
+                        scanList.add(scanResult);
+                    } else {
+                        Log.w(TAG, "misformatted scan result for: " + line);
                     }
-                    break;
-                case DRIVER_HUNG:
-                    Log.e(TAG, "Wifi Driver reports HUNG - reloading.");
-                    /**
-                     * restart the driver - toggle off and on
-                     */
-                    mWM.setWifiEnabled(false);
-                    mWM.setWifiEnabled(true);
-                    break;
                 }
-                noteRunState();
-                break;
-
-            case EVENT_PASSWORD_KEY_MAY_BE_INCORRECT:
-                mPasswordKeyMayBeIncorrect = true;
-                break;
+                lineBeg = lineEnd + 1;
+            }
         }
+
+        mScanResults = scanList;
     }
 
-    private boolean wifiManagerDisableNetwork(int networkId) {
-        boolean disabledNetwork = false;
-        if (0 <= networkId) {
-            disabledNetwork = mWM.disableNetwork(networkId);
-            if (LOCAL_LOGD) {
-                if (disabledNetwork) {
-                    Log.v(TAG, "Disabled network: " + networkId);
+    private void checkAndSetNotification() {
+        // If we shouldn't place a notification on available networks, then
+        // don't bother doing any of the following
+        if (!mNotificationEnabled) return;
+
+        State state = mNetworkInfo.getState();
+        if ((state == NetworkInfo.State.DISCONNECTED)
+                || (state == NetworkInfo.State.UNKNOWN)) {
+            // Look for an open network
+            List<ScanResult> scanResults = mScanResults;
+            if (scanResults != null) {
+                int numOpenNetworks = 0;
+                for (int i = scanResults.size() - 1; i >= 0; i--) {
+                    ScanResult scanResult = scanResults.get(i);
+
+                    if (TextUtils.isEmpty(scanResult.capabilities)) {
+                        numOpenNetworks++;
+                    }
+                }
+
+                if (numOpenNetworks > 0) {
+                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+                        /*
+                         * We've scanned continuously at least
+                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+                         * probably does not have a remembered network in range,
+                         * since otherwise supplicant would have tried to
+                         * associate and thus resetting this counter.
+                         */
+                        setNotificationVisible(true, numOpenNetworks, false, 0);
+                    }
+                    return;
                 }
             }
         }
-        if (LOCAL_LOGD) {
-            if (!disabledNetwork) {
-                Log.e(TAG, "Failed to disable network:" +
-                      " invalid network id: " + networkId);
-            }
-        }
-        return disabledNetwork;
+
+        // No open networks in range, remove the notification
+        setNotificationVisible(false, 0, false, 0);
     }
 
+    /**
+     * Display or don't display a notification that there are open Wi-Fi networks.
+     * @param visible {@code true} if notification should be visible, {@code false} otherwise
+     * @param numNetworks the number networks seen
+     * @param force {@code true} to force notification to be shown/not-shown,
+     * even if it is already shown/not-shown.
+     * @param delay time in milliseconds after which the notification should be made
+     * visible or invisible.
+     */
+    private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+            int delay) {
+
+        // Since we use auto cancel on the notification, when the
+        // mNetworksAvailableNotificationShown is true, the notification may
+        // have actually been canceled.  However, when it is false we know
+        // for sure that it is not being shown (it will not be shown any other
+        // place than here)
+
+        // If it should be hidden and it is already hidden, then noop
+        if (!visible && !mNotificationShown && !force) {
+            return;
+        }
+
+        Message message;
+        if (visible) {
+
+            // Not enough time has passed to show the notification again
+            if (System.currentTimeMillis() < mNotificationRepeatTime) {
+                return;
+            }
+
+            if (mNotification == null) {
+                // Cache the Notification mainly so we can remove the
+                // EVENT_NOTIFICATION_CHANGED message with this Notification from
+                // the queue later
+                mNotification = new Notification();
+                mNotification.when = 0;
+                mNotification.icon = ICON_NETWORKS_AVAILABLE;
+                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+                mNotification.contentIntent = PendingIntent.getActivity(mContext, 0,
+                        new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0);
+            }
+
+            CharSequence title = mContext.getResources().getQuantityText(
+                    com.android.internal.R.plurals.wifi_available, numNetworks);
+            CharSequence details = mContext.getResources().getQuantityText(
+                    com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
+            mNotification.tickerText = title;
+            mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
+
+            mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
+
+            message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1,
+                    ICON_NETWORKS_AVAILABLE, mNotification);
+
+        } else {
+
+            // Remove any pending messages to show the notification
+            mCsHandler.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification);
+
+            message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0,
+                    ICON_NETWORKS_AVAILABLE);
+        }
+
+        mCsHandler.sendMessageDelayed(message, delay);
+
+        mNotificationShown = visible;
+    }
 
     private void configureNetworkProperties() {
         try {
@@ -1328,1146 +1518,7 @@
         // TODO - add proxy info
     }
 
-    private void configureInterface() {
-        checkPollTimer();
-        mLastSignalLevel = -1;
-        if (!mUseStaticIp) {
-            if (!mHaveIpAddress && !mObtainingIpAddress) {
-                mObtainingIpAddress = true;
-                mDhcpTarget.sendEmptyMessage(EVENT_DHCP_START);
-            }
-        } else {
-            int event;
-            if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
-                event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
-                if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration succeeded");
-            } else {
-                event = EVENT_INTERFACE_CONFIGURATION_FAILED;
-                if (LOCAL_LOGD) Log.v(TAG, "Static IP configuration failed");
-            }
-            sendEmptyMessage(event);
-        }
-    }
-
-    /**
-     * Reset our IP state and send out broadcasts following a disconnect.
-     * @param newState the {@code DetailedState} to set. Should be either
-     * {@code DISCONNECTED} or {@code FAILED}.
-     * @param disableInterface indicates whether the interface should
-     * be disabled
-     */
-    private void handleDisconnectedState(DetailedState newState, boolean disableInterface) {
-        if (mDisconnectPending) {
-            cancelDisconnect();
-        }
-        mDisconnectExpected = false;
-        resetConnections(disableInterface);
-        setDetailedState(newState);
-        sendNetworkStateChangeBroadcast(mLastBssid);
-        mWifiInfo.setBSSID(null);
-        mLastBssid = null;
-        mLastSsid = null;
-        mDisconnectPending = false;
-    }
-
-    /**
-     * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
-     * using the interface, stopping DHCP, and disabling the interface.
-     */
-    public void resetConnections(boolean disableInterface) {
-        if (LOCAL_LOGD) Log.d(TAG, "Reset connections and stopping DHCP");
-        mNetworkProperties.clear();
-        mHaveIpAddress = false;
-        mObtainingIpAddress = false;
-        mWifiInfo.setIpAddress(0);
-
-        /*
-         * Reset connection depends on both the interface and the IP assigned,
-         * so it should be done before any chance of the IP being lost.
-         */
-        NetworkUtils.resetConnections(mInterfaceName);
-
-        // Stop DHCP
-        if (mDhcpTarget != null) {
-            mDhcpTarget.setCancelCallback(true);
-            mDhcpTarget.removeMessages(EVENT_DHCP_START);
-        }
-        if (!NetworkUtils.stopDhcp(mInterfaceName)) {
-            Log.e(TAG, "Could not stop DHCP");
-        }
-
-        /**
-         * Interface is re-enabled in the supplicant
-         * when moving out of ASSOCIATING state
-         */
-        if(disableInterface) {
-            if (LOCAL_LOGD) Log.d(TAG, "Disabling interface");
-            NetworkUtils.disableInterface(mInterfaceName);
-        }
-    }
-
-    /**
-     * The supplicant is reporting that we are disconnected from the current
-     * access point. Often, however, a disconnect will be followed very shortly
-     * by a reconnect to the same access point. Therefore, we delay resetting
-     * the connection's IP state for a bit.
-     */
-    private void scheduleDisconnect() {
-        mDisconnectPending = true;
-        if (!hasMessages(EVENT_DEFERRED_DISCONNECT)) {
-            sendEmptyMessageDelayed(EVENT_DEFERRED_DISCONNECT, DISCONNECT_DELAY_MSECS);
-        }
-    }
-
-    private void cancelDisconnect() {
-        mDisconnectPending = false;
-        removeMessages(EVENT_DEFERRED_DISCONNECT);
-    }
-
-    public DhcpInfo getDhcpInfo() {
-        return mDhcpInfo;
-    }
-
-    public synchronized List<ScanResult> getScanResultsList() {
-        return mScanResults;
-    }
-
-    public synchronized void setScanResultsList(List<ScanResult> scanList) {
-        mScanResults = scanList;
-    }
-
-    /**
-     * Get status information for the current connection, if any.
-     * @return a {@link WifiInfo} object containing information about the current connection
-     */
-    public WifiInfo requestConnectionInfo() {
-        requestConnectionStatus(mWifiInfo);
-        requestPolledInfo(mWifiInfo, false);
-        return mWifiInfo;
-    }
-
-    private void requestConnectionStatus(WifiInfo info) {
-        String reply = status();
-        if (reply == null) {
-            return;
-        }
-        /*
-         * Parse the reply from the supplicant to the status command, and update
-         * local state accordingly. The reply is a series of lines of the form
-         * "name=value".
-         */
-        String SSID = null;
-        String BSSID = null;
-        String suppState = null;
-        int netId = -1;
-        String[] lines = reply.split("\n");
-        for (String line : lines) {
-            String[] prop = line.split(" *= *", 2);
-            if (prop.length < 2)
-                continue;
-            String name = prop[0];
-            String value = prop[1];
-            if (name.equalsIgnoreCase("id"))
-                netId = Integer.parseInt(value);
-            else if (name.equalsIgnoreCase("ssid"))
-                SSID = value;
-            else if (name.equalsIgnoreCase("bssid"))
-                BSSID = value;
-            else if (name.equalsIgnoreCase("wpa_state"))
-                suppState = value;
-        }
-        info.setNetworkId(netId);
-        info.setSSID(SSID);
-        info.setBSSID(BSSID);
-        /*
-         * We only set the supplicant state if the previous state was
-         * UNINITIALIZED. This should only happen when we first connect to
-         * the supplicant. Once we're connected, we should always receive
-         * an event upon any state change, but in this case, we want to
-         * make sure any listeners are made aware of the state change.
-         */
-        if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED && suppState != null)
-            setSupplicantState(suppState);
-    }
-
-    /**
-     * Get the dynamic information that is not reported via events.
-     * @param info the object into which the information should be captured.
-     */
-    private synchronized void requestPolledInfo(WifiInfo info, boolean polling)
-    {
-        int newRssi = (polling ? getRssiApprox() : getRssi());
-        if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values
-            /* some implementations avoid negative values by adding 256
-             * so we need to adjust for that here.
-             */
-            if (newRssi > 0) newRssi -= 256;
-            info.setRssi(newRssi);
-            /*
-             * Rather then sending the raw RSSI out every time it
-             * changes, we precalculate the signal level that would
-             * be displayed in the status bar, and only send the
-             * broadcast if that much more coarse-grained number
-             * changes. This cuts down greatly on the number of
-             * broadcasts, at the cost of not informing others
-             * interested in RSSI of all the changes in signal
-             * level.
-             */
-            // TODO: The second arg to the call below needs to be a symbol somewhere, but
-            // it's actually the size of an array of icons that's private
-            // to StatusBar Policy.
-            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4);
-            if (newSignalLevel != mLastSignalLevel) {
-                sendRssiChangeBroadcast(newRssi);
-            }
-            mLastSignalLevel = newSignalLevel;
-        } else {
-            info.setRssi(-200);
-        }
-        int newLinkSpeed = getLinkSpeed();
-        if (newLinkSpeed != -1) {
-            info.setLinkSpeed(newLinkSpeed);
-        }
-    }
-
-    private void sendRssiChangeBroadcast(final int newRssi) {
-        if (ActivityManagerNative.isSystemReady()) {
-            Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
-            intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
-            mContext.sendBroadcast(intent);
-        }
-    }
-
-    private void sendNetworkStateChangeBroadcast(String bssid) {
-        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
-        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
-        if (bssid != null)
-            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
-        mContext.sendStickyBroadcast(intent);
-    }
-
-    /**
-     * Disable Wi-Fi connectivity by stopping the driver.
-     */
-    public boolean teardown() {
-        if (!mTornDownByConnMgr) {
-            if (disconnectAndStop()) {
-                setTornDownByConnMgr(true);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     * Reenable Wi-Fi connectivity by restarting the driver.
-     */
-    public boolean reconnect() {
-        if (mTornDownByConnMgr) {
-            if (restart()) {
-                setTornDownByConnMgr(false);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return true;
-        }
-    }
-
-    /**
-     * We want to stop the driver, but if we're connected to a network,
-     * we first want to disconnect, so that the supplicant is always in
-     * a known state (DISCONNECTED) when the driver is stopped.
-     * @return {@code true} if the operation succeeds, which means that the
-     * disconnect or stop command was initiated.
-     */
-    public synchronized boolean disconnectAndStop() {
-        boolean ret = true;;
-        if (mRunState != RUN_STATE_STOPPING && mRunState != RUN_STATE_STOPPED) {
-            // Take down any open network notifications
-            setNotificationVisible(false, 0, false, 0);
-
-            if (mWifiInfo.getSupplicantState() == SupplicantState.DORMANT) {
-                ret = stopDriver();
-            } else {
-                ret = disconnect();
-            }
-            mRunState = RUN_STATE_STOPPING;
-        }
-        return ret;
-    }
-
-    public synchronized boolean restart() {
-        if (mRunState == RUN_STATE_STOPPED) {
-            mRunState = RUN_STATE_STARTING;
-            resetConnections(true);
-            return startDriver();
-        } else if (mRunState == RUN_STATE_STOPPING) {
-            mRunState = RUN_STATE_STARTING;
-        }
-        return true;
-    }
-
-    public int getWifiState() {
-        return mWifiState.get();
-    }
-
-    public void setWifiState(int wifiState) {
-        mWifiState.set(wifiState);
-    }
-
-   /**
-     * The WifiNative interface functions are listed below.
-     * The only native call that is not synchronized on
-     * WifiStateTracker is waitForEvent() which waits on a
-     * seperate monitor channel.
-     *
-     * All supplicant commands need the wifi to be in an
-     * enabled state. This can be done by checking the
-     * mWifiState to be WIFI_STATE_ENABLED.
-     *
-     * All commands that can cause commands to driver
-     * initiated need the driver state to be started.
-     * This is done by checking isDriverStopped() to
-     * be false.
-     */
-
-    /**
-     * Load the driver and firmware
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean loadDriver() {
-        return WifiNative.loadDriver();
-    }
-
-    /**
-     * Unload the driver and firmware
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean unloadDriver() {
-        return WifiNative.unloadDriver();
-    }
-
-    /**
-     * Check the supplicant config and
-     * start the supplicant daemon
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean startSupplicant() {
-        return WifiNative.startSupplicant();
-    }
-
-    /**
-     * Stop the supplicant daemon
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean stopSupplicant() {
-        return WifiNative.stopSupplicant();
-    }
-
-    /**
-     * Establishes two channels - control channel for commands
-     * and monitor channel for notifying WifiMonitor
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean connectToSupplicant() {
-        return WifiNative.connectToSupplicant();
-    }
-
-    /**
-     * Close the control/monitor channels to supplicant
-     */
-    public synchronized void closeSupplicantConnection() {
-        WifiNative.closeSupplicantConnection();
-    }
-
-    /**
-     * Check if the supplicant is alive
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean ping() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.pingCommand();
-    }
-
-    /**
-     * initiate an active or passive scan
-     *
-     * @param forceActive true if it is a active scan
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean scan(boolean forceActive) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.scanCommand(forceActive);
-    }
-
-    /**
-     * Specifies whether the supplicant or driver
-     * take care of initiating scan and doing AP selection
-     *
-     * @param mode
-     *    SUPPL_SCAN_HANDLING_NORMAL
-     *    SUPPL_SCAN_HANDLING_LIST_ONLY
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setScanResultHandling(int mode) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.setScanResultHandlingCommand(mode);
-    }
-
-    /**
-     * Fetch the scan results from the supplicant
-     *
-     * @return example result string
-     * 00:bb:cc:dd:cc:ee       2427    166     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net1
-     * 00:bb:cc:dd:cc:ff       2412    165     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net2
-     */
-    public synchronized String scanResults() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return null;
-        }
-        return WifiNative.scanResultsCommand();
-    }
-
-    /**
-     * Set the scan mode - active or passive
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setScanMode(boolean isScanModeActive) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        if (mIsScanModeActive != isScanModeActive) {
-            return WifiNative.setScanModeCommand(mIsScanModeActive = isScanModeActive);
-        }
-        return true;
-    }
-
-    /**
-     * Disconnect from Access Point
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean disconnect() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.disconnectCommand();
-    }
-
-    /**
-     * Initiate a reconnection to AP
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean reconnectCommand() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.reconnectCommand();
-    }
-
-    /**
-     * Add a network
-     *
-     * @return network id of the new network
-     */
-    public synchronized int addNetwork() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return -1;
-        }
-        return WifiNative.addNetworkCommand();
-    }
-
-    /**
-     * Delete a network
-     *
-     * @param networkId id of the network to be removed
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean removeNetwork(int networkId) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return mDisconnectExpected = WifiNative.removeNetworkCommand(networkId);
-    }
-
-    /**
-     * Enable a network
-     *
-     * @param netId network id of the network
-     * @param disableOthers true, if all other networks have to be disabled
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean enableNetwork(int netId, boolean disableOthers) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.enableNetworkCommand(netId, disableOthers);
-    }
-
-    /**
-     * Disable a network
-     *
-     * @param netId network id of the network
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean disableNetwork(int netId) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.disableNetworkCommand(netId);
-    }
-
-    /**
-     * Initiate a re-association in supplicant
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean reassociate() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.reassociateCommand();
-    }
-
-    /**
-     * Blacklist a BSSID. This will avoid the AP if there are
-     * alternate APs to connect
-     *
-     * @param bssid BSSID of the network
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean addToBlacklist(String bssid) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.addToBlacklistCommand(bssid);
-    }
-
-    /**
-     * Clear the blacklist list
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean clearBlacklist() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.clearBlacklistCommand();
-    }
-
-    /**
-     * List all configured networks
-     *
-     * @return list of networks or null on failure
-     */
-    public synchronized String listNetworks() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.listNetworksCommand();
-    }
-
-    /**
-     * Get network setting by name
-     *
-     * @param netId network id of the network
-     * @param name network variable key
-     * @return value corresponding to key
-     */
-    public synchronized String getNetworkVariable(int netId, String name) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.getNetworkVariableCommand(netId, name);
-    }
-
-    /**
-     * Set network setting by name
-     *
-     * @param netId network id of the network
-     * @param name network variable key
-     * @param value network variable value
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setNetworkVariable(int netId, String name, String value) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.setNetworkVariableCommand(netId, name, value);
-    }
-
-    /**
-     * Get detailed status of the connection
-     *
-     * @return Example status result
-     *  bssid=aa:bb:cc:dd:ee:ff
-     *  ssid=TestNet
-     *  id=3
-     *  pairwise_cipher=NONE
-     *  group_cipher=NONE
-     *  key_mgmt=NONE
-     *  wpa_state=COMPLETED
-     *  ip_address=X.X.X.X
-     */
-    public synchronized String status() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return null;
-        }
-        return WifiNative.statusCommand();
-    }
-
-    /**
-     * Get RSSI to currently connected network
-     *
-     * @return RSSI value, -1 on failure
-     */
-    public synchronized int getRssi() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getRssiApproxCommand();
-    }
-
-    /**
-     * Get approx RSSI to currently connected network
-     *
-     * @return RSSI value, -1 on failure
-     */
-    public synchronized int getRssiApprox() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getRssiApproxCommand();
-    }
-
-    /**
-     * Get link speed to currently connected network
-     *
-     * @return link speed, -1 on failure
-     */
-    public synchronized int getLinkSpeed() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getLinkSpeedCommand();
-    }
-
-    /**
-     * Get MAC address of radio
-     *
-     * @return MAC address, null on failure
-     */
-    public synchronized String getMacAddress() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return null;
-        }
-        return WifiNative.getMacAddressCommand();
-    }
-
-    /**
-     * Start driver
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean startDriver() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.startDriverCommand();
-    }
-
-    /**
-     * Stop driver
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean stopDriver() {
-        /* Driver stop should not happen only when supplicant event
-         * DRIVER_STOPPED has already been handled */
-        if (mWifiState.get() != WIFI_STATE_ENABLED || mRunState == RUN_STATE_STOPPED) {
-            return false;
-        }
-        return WifiNative.stopDriverCommand();
-    }
-
-    /**
-     * Start packet filtering
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean startPacketFiltering() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.startPacketFiltering();
-    }
-
-    /**
-     * Stop packet filtering
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean stopPacketFiltering() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.stopPacketFiltering();
-    }
-
-    /**
-     * Get power mode
-     * @return power mode
-     */
-    public synchronized int getPowerMode() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED && !isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getPowerModeCommand();
-    }
-
-    /**
-     * Set power mode
-     * @param mode
-     *     DRIVER_POWER_MODE_AUTO
-     *     DRIVER_POWER_MODE_ACTIVE
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setPowerMode(int mode) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.setPowerModeCommand(mode);
-    }
-
-    /**
-     * Set the number of allowed radio frequency channels from the system
-     * setting value, if any.
-     * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
-     * the number of channels is invalid.
-     */
-    public synchronized boolean setNumAllowedChannels() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        try {
-            return setNumAllowedChannels(
-                    Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
-        } catch (Settings.SettingNotFoundException e) {
-            if (mNumAllowedChannels != 0) {
-                WifiNative.setNumAllowedChannelsCommand(mNumAllowedChannels);
-            }
-            // otherwise, use the driver default
-        }
-        return true;
-    }
-
-    /**
-     * Set the number of radio frequency channels that are allowed to be used
-     * in the current regulatory domain.
-     * @param numChannels the number of allowed channels. Must be greater than 0
-     * and less than or equal to 16.
-     * @return {@code true} if the operation succeeds, {@code false} otherwise, e.g.,
-     * {@code numChannels} is outside the valid range.
-     */
-    public synchronized boolean setNumAllowedChannels(int numChannels) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        mNumAllowedChannels = numChannels;
-        return WifiNative.setNumAllowedChannelsCommand(numChannels);
-    }
-
-    /**
-     * Get number of allowed channels
-     *
-     * @return channel count, -1 on failure
-     */
-    public synchronized int getNumAllowedChannels() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return -1;
-        }
-        return WifiNative.getNumAllowedChannelsCommand();
-    }
-
-    /**
-     * Set bluetooth coex mode:
-     *
-     * @param mode
-     *  BLUETOOTH_COEXISTENCE_MODE_ENABLED
-     *  BLUETOOTH_COEXISTENCE_MODE_DISABLED
-     *  BLUETOOTH_COEXISTENCE_MODE_SENSE
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean setBluetoothCoexistenceMode(int mode) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return false;
-        }
-        return WifiNative.setBluetoothCoexistenceModeCommand(mode);
-    }
-
-    /**
-     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
-     * some of the low-level scan parameters used by the driver are changed to
-     * reduce interference with A2DP streaming.
-     *
-     * @param isBluetoothPlaying whether to enable or disable this mode
-     */
-    public synchronized void setBluetoothScanMode(boolean isBluetoothPlaying) {
-        if (mWifiState.get() != WIFI_STATE_ENABLED || isDriverStopped()) {
-            return;
-        }
-        WifiNative.setBluetoothCoexistenceScanModeCommand(isBluetoothPlaying);
-    }
-
-    /**
-     * Save configuration on supplicant
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean saveConfig() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.saveConfigCommand();
-    }
-
-    /**
-     * Reload the configuration from file
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     */
-    public synchronized boolean reloadConfig() {
-        if (mWifiState.get() != WIFI_STATE_ENABLED) {
-            return false;
-        }
-        return WifiNative.reloadConfigCommand();
-    }
-
-    public boolean setRadio(boolean turnOn) {
-        return mWM.setWifiEnabled(turnOn);
-    }
-
-    /**
-     * {@inheritDoc}
-     * There are currently no Wi-Fi-specific features supported.
-     * @param feature the name of the feature
-     * @return {@code -1} indicating failure, always
-     */
-    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
-        return -1;
-    }
-
-    /**
-     * {@inheritDoc}
-     * There are currently no Wi-Fi-specific features supported.
-     * @param feature the name of the feature
-     * @return {@code -1} indicating failure, always
-     */
-    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
-        return -1;
-    }
-
-    public void interpretScanResultsAvailable() {
-
-        // If we shouldn't place a notification on available networks, then
-        // don't bother doing any of the following
-        if (!mNotificationEnabled) return;
-
-        NetworkInfo networkInfo = getNetworkInfo();
-
-        State state = networkInfo.getState();
-        if ((state == NetworkInfo.State.DISCONNECTED)
-                || (state == NetworkInfo.State.UNKNOWN)) {
-
-            // Look for an open network
-            List<ScanResult> scanResults = getScanResultsList();
-            if (scanResults != null) {
-                int numOpenNetworks = 0;
-                for (int i = scanResults.size() - 1; i >= 0; i--) {
-                    ScanResult scanResult = scanResults.get(i);
-
-                    if (TextUtils.isEmpty(scanResult.capabilities)) {
-                        numOpenNetworks++;
-                    }
-                }
-            
-                if (numOpenNetworks > 0) {
-                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
-                        /*
-                         * We've scanned continuously at least
-                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
-                         * probably does not have a remembered network in range,
-                         * since otherwise supplicant would have tried to
-                         * associate and thus resetting this counter.
-                         */
-                        setNotificationVisible(true, numOpenNetworks, false, 0);
-                    }
-                    return;
-                }
-            }
-        }
-        
-        // No open networks in range, remove the notification
-        setNotificationVisible(false, 0, false, 0);
-    }
-
-    /**
-     * Send a  notification that the results of a scan for network access
-     * points has completed, and results are available.
-     */
-    private void sendScanResultsAvailable() {
-        Message msg = mTarget.obtainMessage(EVENT_SCAN_RESULTS_AVAILABLE, mNetworkInfo);
-        msg.sendToTarget();
-    }
-
-    /**
-     * Display or don't display a notification that there are open Wi-Fi networks.
-     * @param visible {@code true} if notification should be visible, {@code false} otherwise
-     * @param numNetworks the number networks seen
-     * @param force {@code true} to force notification to be shown/not-shown,
-     * even if it is already shown/not-shown.
-     * @param delay time in milliseconds after which the notification should be made
-     * visible or invisible.
-     */
-    public void setNotificationVisible(boolean visible, int numNetworks, boolean force, int delay) {
-        
-        // Since we use auto cancel on the notification, when the
-        // mNetworksAvailableNotificationShown is true, the notification may
-        // have actually been canceled.  However, when it is false we know
-        // for sure that it is not being shown (it will not be shown any other
-        // place than here)
-        
-        // If it should be hidden and it is already hidden, then noop
-        if (!visible && !mNotificationShown && !force) {
-            return;
-        }
-
-        Message message;
-        if (visible) {
-            
-            // Not enough time has passed to show the notification again
-            if (System.currentTimeMillis() < mNotificationRepeatTime) {
-                return;
-            }
-            
-            if (mNotification == null) {
-                // Cache the Notification mainly so we can remove the
-                // EVENT_NOTIFICATION_CHANGED message with this Notification from
-                // the queue later
-                mNotification = new Notification();
-                mNotification.when = 0;
-                mNotification.icon = ICON_NETWORKS_AVAILABLE;
-                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
-                mNotification.contentIntent = PendingIntent.getActivity(mContext, 0,
-                        new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), 0);
-            }
-
-            CharSequence title = mContext.getResources().getQuantityText(
-                    com.android.internal.R.plurals.wifi_available, numNetworks);
-            CharSequence details = mContext.getResources().getQuantityText(
-                    com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
-            mNotification.tickerText = title;
-            mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
-            
-            mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
-
-            message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1,
-                    ICON_NETWORKS_AVAILABLE, mNotification);
-            
-        } else {
-
-            // Remove any pending messages to show the notification
-            mTarget.removeMessages(EVENT_NOTIFICATION_CHANGED, mNotification);
-            
-            message = mTarget.obtainMessage(EVENT_NOTIFICATION_CHANGED, 0, ICON_NETWORKS_AVAILABLE);
-        }
-
-        mTarget.sendMessageDelayed(message, delay);
-        
-        mNotificationShown = visible;
-    }
-
-    /**
-     * Clears variables related to tracking whether a notification has been
-     * shown recently.
-     * <p>
-     * After calling this method, the timer that prevents notifications from
-     * being shown too often will be cleared.
-     */
-    private void resetNotificationTimer() {
-        mNotificationRepeatTime = 0;
-        mNumScansSinceNetworkStateChange = 0;
-    }
-
-    @Override
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-
-        sb.append(mNetworkProperties.toString());
-        sb.append(" runState=");
-        if (mRunState >= 1 && mRunState <= mRunStateNames.length) {
-            sb.append(mRunStateNames[mRunState-1]);
-        } else {
-            sb.append(mRunState);
-        }
-        sb.append(LS).append(mWifiInfo).append(LS);
-        sb.append(mDhcpInfo).append(LS);
-        sb.append("haveIpAddress=").append(mHaveIpAddress).
-                append(", obtainingIpAddress=").append(mObtainingIpAddress).
-                append(", scanModeActive=").append(mIsScanModeActive).append(LS).
-                append("lastSignalLevel=").append(mLastSignalLevel).
-                append(", explicitlyDisabled=").append(mTornDownByConnMgr);
-        return sb.toString();
-    }
-
-    private class DhcpHandler extends Handler {
-
-        private Handler mTarget;
-        
-        /**
-         * Whether to skip the DHCP result callback to the target. For example,
-         * this could be set if the network we were requesting an IP for has
-         * since been disconnected.
-         * <p>
-         * Note: There is still a chance where the client's intended DHCP
-         * request not being canceled. For example, we are request for IP on
-         * A, and he queues request for IP on B, and then cancels the request on
-         * B while we're still requesting from A.
-         */
-        private boolean mCancelCallback;
-
-        /**
-         * Instance of the bluetooth headset helper. This needs to be created
-         * early because there is a delay before it actually 'connects', as
-         * noted by its javadoc. If we check before it is connected, it will be
-         * in an error state and we will not disable coexistence.
-         */
-        private BluetoothHeadset mBluetoothHeadset;
-        
-        public DhcpHandler(Looper looper, Handler target) {
-            super(looper);
-            mTarget = target;
-            
-            mBluetoothHeadset = new BluetoothHeadset(mContext, null);
-        }
-
-        public void handleMessage(Message msg) {
-            int event;
-
-            switch (msg.what) {
-                case EVENT_DHCP_START:
-                    
-                    boolean modifiedBluetoothCoexistenceMode = false;
-                    int powerMode = DRIVER_POWER_MODE_AUTO;
-
-                    if (shouldDisableCoexistenceMode()) {
-                        /*
-                         * There are problems setting the Wi-Fi driver's power
-                         * mode to active when bluetooth coexistence mode is
-                         * enabled or sense.
-                         * <p>
-                         * We set Wi-Fi to active mode when
-                         * obtaining an IP address because we've found
-                         * compatibility issues with some routers with low power
-                         * mode.
-                         * <p>
-                         * In order for this active power mode to properly be set,
-                         * we disable coexistence mode until we're done with
-                         * obtaining an IP address.  One exception is if we
-                         * are currently connected to a headset, since disabling
-                         * coexistence would interrupt that connection.
-                         */
-                        modifiedBluetoothCoexistenceMode = true;
-
-                        // Disable the coexistence mode
-                        setBluetoothCoexistenceMode(
-                                WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
-                    }
-
-                    powerMode = getPowerMode();
-                    if (powerMode < 0) {
-                      // Handle the case where supplicant driver does not support
-                      // getPowerModeCommand.
-                        powerMode = DRIVER_POWER_MODE_AUTO;
-                    }
-                    if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
-                        setPowerMode(DRIVER_POWER_MODE_ACTIVE);
-                    }
-
-                    synchronized (this) {
-                        // A new request is being made, so assume we will callback
-                        mCancelCallback = false;
-                    }
-                    Log.d(TAG, "DhcpHandler: DHCP request started");
-                    if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
-                        event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
-                        if (LOCAL_LOGD) Log.v(TAG, "DhcpHandler: DHCP request succeeded");
-                    } else {
-                        event = EVENT_INTERFACE_CONFIGURATION_FAILED;
-                        Log.i(TAG, "DhcpHandler: DHCP request failed: " +
-                            NetworkUtils.getDhcpError());
-                    }
-
-                    if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
-                        setPowerMode(powerMode);
-                    }
-
-                    if (modifiedBluetoothCoexistenceMode) {
-                        // Set the coexistence mode back to its default value
-                        setBluetoothCoexistenceMode(
-                                WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
-                    }
-
-                    synchronized (this) {
-                        if (!mCancelCallback) {
-                            mTarget.sendEmptyMessage(event);
-                        }
-                    }
-                    break;
-            }
-        }
-
-        public synchronized void setCancelCallback(boolean cancelCallback) {
-            mCancelCallback = cancelCallback;
-        }
 
-        /**
-         * Whether to disable coexistence mode while obtaining IP address. This
-         * logic will return true only if the current bluetooth
-         * headset/handsfree state is disconnected. This means if it is in an
-         * error state, we will NOT disable coexistence mode to err on the side
-         * of safety.
-         * 
-         * @return Whether to disable coexistence mode.
-         */
-        private boolean shouldDisableCoexistenceMode() {
-            int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset());
-            return state == BluetoothHeadset.STATE_DISCONNECTED;
-        }
-    }
-    
     private void checkUseStaticIp() {
         mUseStaticIp = false;
         final ContentResolver cr = mContext.getContentResolver();
@@ -2558,6 +1609,7 @@
                 Settings.System.WIFI_STATIC_DNS2), false, this);
         }
 
+        @Override
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
 
@@ -2587,15 +1639,2300 @@
                         oDns2 != mDhcpInfo.dns2));
 
             if (changed) {
-                resetConnections(true);
-                configureInterface();
+                sendMessage(CMD_RECONFIGURE_IP);
                 if (mUseStaticIp) {
-                    mTarget.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
+                    mCsHandler.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
                 }
             }
         }
     }
 
+
+    /**
+     * Clears variables related to tracking whether a notification has been
+     * shown recently.
+     * <p>
+     * After calling this method, the timer that prevents notifications from
+     * being shown too often will be cleared.
+     */
+    private void resetNotificationTimer() {
+        mNotificationRepeatTime = 0;
+        mNumScansSinceNetworkStateChange = 0;
+    }
+
+
+    /**
+     * Whether to disable coexistence mode while obtaining IP address. This
+     * logic will return true only if the current bluetooth
+     * headset/handsfree state is disconnected. This means if it is in an
+     * error state, we will NOT disable coexistence mode to err on the side
+     * of safety.
+     *
+     * @return Whether to disable coexistence mode.
+     */
+    private boolean shouldDisableCoexistenceMode() {
+        int state = mBluetoothHeadset.getState(mBluetoothHeadset.getCurrentHeadset());
+        return state == BluetoothHeadset.STATE_DISCONNECTED;
+    }
+
+    private void checkIsBluetoothPlaying() {
+        boolean isBluetoothPlaying = false;
+        Set<BluetoothDevice> connected = mBluetoothA2dp.getConnectedSinks();
+
+        for (BluetoothDevice device : connected) {
+            if (mBluetoothA2dp.getSinkState(device) == BluetoothA2dp.STATE_PLAYING) {
+                isBluetoothPlaying = true;
+                break;
+            }
+        }
+        setBluetoothScanMode(isBluetoothPlaying);
+    }
+
+    private void sendScanResultsAvailableBroadcast() {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+    }
+
+    private void sendRssiChangeBroadcast(final int newRssi) {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        Intent intent = new Intent(WifiManager.RSSI_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_NEW_RSSI, newRssi);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void sendNetworkStateChangeBroadcast(String bssid) {
+        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, mNetworkInfo);
+        if (bssid != null)
+            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) {
+        Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state);
+        if (failedAuth) {
+            intent.putExtra(
+                WifiManager.EXTRA_SUPPLICANT_ERROR,
+                WifiManager.ERROR_AUTHENTICATING);
+        }
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+        intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Record the detailed state of a network, and if it is a
+     * change from the previous state, send a notification to
+     * any listeners.
+     * @param state the new @{code DetailedState}
+     */
+    private void setDetailedState(NetworkInfo.DetailedState state) {
+        Log.d(TAG, "setDetailed state, old ="
+                + mNetworkInfo.getDetailedState() + " and new state=" + state);
+        if (state != mNetworkInfo.getDetailedState()) {
+            mNetworkInfo.setDetailedState(state, null, null);
+            Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+            msg.sendToTarget();
+        }
+    }
+
+    private static String removeDoubleQuotes(String string) {
+      if (string.length() <= 2) return "";
+      return string.substring(1, string.length() - 1);
+    }
+
+    private static String convertToQuotedString(String string) {
+        return "\"" + string + "\"";
+    }
+
+    private static String makeString(BitSet set, String[] strings) {
+        StringBuffer buf = new StringBuffer();
+        int nextSetBit = -1;
+
+        /* Make sure all set bits are in [0, strings.length) to avoid
+         * going out of bounds on strings.  (Shouldn't happen, but...) */
+        set = set.get(0, strings.length);
+
+        while ((nextSetBit = set.nextSetBit(nextSetBit + 1)) != -1) {
+            buf.append(strings[nextSetBit].replace('_', '-')).append(' ');
+        }
+
+        // remove trailing space
+        if (set.cardinality() > 0) {
+            buf.setLength(buf.length() - 1);
+        }
+
+        return buf.toString();
+    }
+
+    private static int lookupString(String string, String[] strings) {
+        int size = strings.length;
+
+        string = string.replace('-', '_');
+
+        for (int i = 0; i < size; i++)
+            if (string.equals(strings[i]))
+                return i;
+
+        // if we ever get here, we should probably add the
+        // value to WifiConfiguration to reflect that it's
+        // supported by the WPA supplicant
+        Log.w(TAG, "Failed to look-up a string: " + string);
+
+        return -1;
+    }
+
+    private int addOrUpdateNetworkNative(WifiConfiguration config) {
+        /*
+         * If the supplied networkId is -1, we create a new empty
+         * network configuration. Otherwise, the networkId should
+         * refer to an existing configuration.
+         */
+        int netId = config.networkId;
+        boolean newNetwork = netId == -1;
+        // networkId of -1 means we want to create a new network
+
+        if (newNetwork) {
+            netId = WifiNative.addNetworkCommand();
+            if (netId < 0) {
+                Log.e(TAG, "Failed to add a network!");
+                return -1;
+          }
+        }
+
+        setVariables: {
+
+            if (config.SSID != null &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.ssidVarName,
+                        config.SSID)) {
+                Log.d(TAG, "failed to set SSID: "+config.SSID);
+                break setVariables;
+            }
+
+            if (config.BSSID != null &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.bssidVarName,
+                        config.BSSID)) {
+                Log.d(TAG, "failed to set BSSID: "+config.BSSID);
+                break setVariables;
+            }
+
+            String allowedKeyManagementString =
+                makeString(config.allowedKeyManagement, WifiConfiguration.KeyMgmt.strings);
+            if (config.allowedKeyManagement.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.KeyMgmt.varName,
+                        allowedKeyManagementString)) {
+                Log.d(TAG, "failed to set key_mgmt: "+
+                        allowedKeyManagementString);
+                break setVariables;
+            }
+
+            String allowedProtocolsString =
+                makeString(config.allowedProtocols, WifiConfiguration.Protocol.strings);
+            if (config.allowedProtocols.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.Protocol.varName,
+                        allowedProtocolsString)) {
+                Log.d(TAG, "failed to set proto: "+
+                        allowedProtocolsString);
+                break setVariables;
+            }
+
+            String allowedAuthAlgorithmsString =
+                makeString(config.allowedAuthAlgorithms, WifiConfiguration.AuthAlgorithm.strings);
+            if (config.allowedAuthAlgorithms.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.AuthAlgorithm.varName,
+                        allowedAuthAlgorithmsString)) {
+                Log.d(TAG, "failed to set auth_alg: "+
+                        allowedAuthAlgorithmsString);
+                break setVariables;
+            }
+
+            String allowedPairwiseCiphersString =
+                    makeString(config.allowedPairwiseCiphers,
+                    WifiConfiguration.PairwiseCipher.strings);
+            if (config.allowedPairwiseCiphers.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.PairwiseCipher.varName,
+                        allowedPairwiseCiphersString)) {
+                Log.d(TAG, "failed to set pairwise: "+
+                        allowedPairwiseCiphersString);
+                break setVariables;
+            }
+
+            String allowedGroupCiphersString =
+                makeString(config.allowedGroupCiphers, WifiConfiguration.GroupCipher.strings);
+            if (config.allowedGroupCiphers.cardinality() != 0 &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.GroupCipher.varName,
+                        allowedGroupCiphersString)) {
+                Log.d(TAG, "failed to set group: "+
+                        allowedGroupCiphersString);
+                break setVariables;
+            }
+
+            // Prevent client screw-up by passing in a WifiConfiguration we gave it
+            // by preventing "*" as a key.
+            if (config.preSharedKey != null && !config.preSharedKey.equals("*") &&
+                    !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.pskVarName,
+                        config.preSharedKey)) {
+                Log.d(TAG, "failed to set psk: "+config.preSharedKey);
+                break setVariables;
+            }
+
+            boolean hasSetKey = false;
+            if (config.wepKeys != null) {
+                for (int i = 0; i < config.wepKeys.length; i++) {
+                    // Prevent client screw-up by passing in a WifiConfiguration we gave it
+                    // by preventing "*" as a key.
+                    if (config.wepKeys[i] != null && !config.wepKeys[i].equals("*")) {
+                        if (!WifiNative.setNetworkVariableCommand(
+                                    netId,
+                                    WifiConfiguration.wepKeyVarNames[i],
+                                    config.wepKeys[i])) {
+                            Log.d(TAG,
+                                    "failed to set wep_key"+i+": " +
+                                    config.wepKeys[i]);
+                            break setVariables;
+                        }
+                        hasSetKey = true;
+                    }
+                }
+            }
+
+            if (hasSetKey) {
+                if (!WifiNative.setNetworkVariableCommand(
+                            netId,
+                            WifiConfiguration.wepTxKeyIdxVarName,
+                            Integer.toString(config.wepTxKeyIndex))) {
+                    Log.d(TAG,
+                            "failed to set wep_tx_keyidx: "+
+                            config.wepTxKeyIndex);
+                    break setVariables;
+                }
+            }
+
+            if (!WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.priorityVarName,
+                        Integer.toString(config.priority))) {
+                Log.d(TAG, config.SSID + ": failed to set priority: "
+                        +config.priority);
+                break setVariables;
+            }
+
+            if (config.hiddenSSID && !WifiNative.setNetworkVariableCommand(
+                        netId,
+                        WifiConfiguration.hiddenSSIDVarName,
+                        Integer.toString(config.hiddenSSID ? 1 : 0))) {
+                Log.d(TAG, config.SSID + ": failed to set hiddenSSID: "+
+                        config.hiddenSSID);
+                break setVariables;
+            }
+
+            for (WifiConfiguration.EnterpriseField field
+                    : config.enterpriseFields) {
+                String varName = field.varName();
+                String value = field.value();
+                if (value != null) {
+                    if (field != config.eap) {
+                        value = (value.length() == 0) ? "NULL" : convertToQuotedString(value);
+                    }
+                    if (!WifiNative.setNetworkVariableCommand(
+                                netId,
+                                varName,
+                                value)) {
+                        Log.d(TAG, config.SSID + ": failed to set " + varName +
+                                ": " + value);
+                        break setVariables;
+                    }
+                }
+            }
+            return netId;
+        }
+
+        if (newNetwork) {
+            WifiNative.removeNetworkCommand(netId);
+            Log.d(TAG,
+                    "Failed to set a network variable, removed network: "
+                    + netId);
+        }
+
+        return -1;
+    }
+
+    private List<WifiConfiguration> getConfiguredNetworksNative() {
+        String listStr = WifiNative.listNetworksCommand();
+
+        List<WifiConfiguration> networks =
+            new ArrayList<WifiConfiguration>();
+        if (listStr == null)
+            return networks;
+
+        String[] lines = listStr.split("\n");
+        // Skip the first line, which is a header
+        for (int i = 1; i < lines.length; i++) {
+            String[] result = lines[i].split("\t");
+            // network-id | ssid | bssid | flags
+            WifiConfiguration config = new WifiConfiguration();
+            try {
+                config.networkId = Integer.parseInt(result[0]);
+            } catch(NumberFormatException e) {
+                continue;
+            }
+            if (result.length > 3) {
+                if (result[3].indexOf("[CURRENT]") != -1)
+                    config.status = WifiConfiguration.Status.CURRENT;
+                else if (result[3].indexOf("[DISABLED]") != -1)
+                    config.status = WifiConfiguration.Status.DISABLED;
+                else
+                    config.status = WifiConfiguration.Status.ENABLED;
+            } else {
+                config.status = WifiConfiguration.Status.ENABLED;
+            }
+            readNetworkVariables(config);
+            networks.add(config);
+        }
+        return networks;
+    }
+
+    /**
+     * Read the variables from the supplicant daemon that are needed to
+     * fill in the WifiConfiguration object.
+     *
+     * @param config the {@link WifiConfiguration} object to be filled in.
+     */
+    private void readNetworkVariables(WifiConfiguration config) {
+
+        int netId = config.networkId;
+        if (netId < 0)
+            return;
+
+        /*
+         * TODO: maybe should have a native method that takes an array of
+         * variable names and returns an array of values. But we'd still
+         * be doing a round trip to the supplicant daemon for each variable.
+         */
+        String value;
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.ssidVarName);
+        if (!TextUtils.isEmpty(value)) {
+            config.SSID = removeDoubleQuotes(value);
+        } else {
+            config.SSID = null;
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.bssidVarName);
+        if (!TextUtils.isEmpty(value)) {
+            config.BSSID = value;
+        } else {
+            config.BSSID = null;
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.priorityVarName);
+        config.priority = -1;
+        if (!TextUtils.isEmpty(value)) {
+            try {
+                config.priority = Integer.parseInt(value);
+            } catch (NumberFormatException ignore) {
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.hiddenSSIDVarName);
+        config.hiddenSSID = false;
+        if (!TextUtils.isEmpty(value)) {
+            try {
+                config.hiddenSSID = Integer.parseInt(value) != 0;
+            } catch (NumberFormatException ignore) {
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.wepTxKeyIdxVarName);
+        config.wepTxKeyIndex = -1;
+        if (!TextUtils.isEmpty(value)) {
+            try {
+                config.wepTxKeyIndex = Integer.parseInt(value);
+            } catch (NumberFormatException ignore) {
+            }
+        }
+
+        for (int i = 0; i < 4; i++) {
+            value = WifiNative.getNetworkVariableCommand(netId,
+                    WifiConfiguration.wepKeyVarNames[i]);
+            if (!TextUtils.isEmpty(value)) {
+                config.wepKeys[i] = value;
+            } else {
+                config.wepKeys[i] = null;
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(netId, WifiConfiguration.pskVarName);
+        if (!TextUtils.isEmpty(value)) {
+            config.preSharedKey = value;
+        } else {
+            config.preSharedKey = null;
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.Protocol.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.Protocol.strings);
+                if (0 <= index) {
+                    config.allowedProtocols.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.KeyMgmt.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.KeyMgmt.strings);
+                if (0 <= index) {
+                    config.allowedKeyManagement.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.AuthAlgorithm.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.AuthAlgorithm.strings);
+                if (0 <= index) {
+                    config.allowedAuthAlgorithms.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.PairwiseCipher.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.PairwiseCipher.strings);
+                if (0 <= index) {
+                    config.allowedPairwiseCiphers.set(index);
+                }
+            }
+        }
+
+        value = WifiNative.getNetworkVariableCommand(config.networkId,
+                WifiConfiguration.GroupCipher.varName);
+        if (!TextUtils.isEmpty(value)) {
+            String vals[] = value.split(" ");
+            for (String val : vals) {
+                int index =
+                    lookupString(val, WifiConfiguration.GroupCipher.strings);
+                if (0 <= index) {
+                    config.allowedGroupCiphers.set(index);
+                }
+            }
+        }
+
+        for (WifiConfiguration.EnterpriseField field :
+                config.enterpriseFields) {
+            value = WifiNative.getNetworkVariableCommand(netId,
+                    field.varName());
+            if (!TextUtils.isEmpty(value)) {
+                if (field != config.eap) value = removeDoubleQuotes(value);
+                field.setValue(value);
+            }
+        }
+
+    }
+
+    /**
+     * Poll for info not reported via events
+     * RSSI & Linkspeed
+     */
+    private void requestPolledInfo() {
+        int newRssi = WifiNative.getRssiCommand();
+        if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values
+            /* some implementations avoid negative values by adding 256
+             * so we need to adjust for that here.
+             */
+            if (newRssi > 0) newRssi -= 256;
+            mWifiInfo.setRssi(newRssi);
+            /*
+             * Rather then sending the raw RSSI out every time it
+             * changes, we precalculate the signal level that would
+             * be displayed in the status bar, and only send the
+             * broadcast if that much more coarse-grained number
+             * changes. This cuts down greatly on the number of
+             * broadcasts, at the cost of not mWifiInforming others
+             * interested in RSSI of all the changes in signal
+             * level.
+             */
+            // TODO: The second arg to the call below needs to be a symbol somewhere, but
+            // it's actually the size of an array of icons that's private
+            // to StatusBar Policy.
+            int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4);
+            if (newSignalLevel != mLastSignalLevel) {
+                sendRssiChangeBroadcast(newRssi);
+            }
+            mLastSignalLevel = newSignalLevel;
+        } else {
+            mWifiInfo.setRssi(-200);
+        }
+        int newLinkSpeed = WifiNative.getLinkSpeedCommand();
+        if (newLinkSpeed != -1) {
+            mWifiInfo.setLinkSpeed(newLinkSpeed);
+        }
+    }
+
+    /**
+     * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
+     * using the interface, stopping DHCP & disabling interface
+     */
+    private void handleNetworkDisconnect() {
+        Log.d(TAG, "Reset connections and stopping DHCP");
+
+        /*
+         * Reset connections & stop DHCP
+         */
+        NetworkUtils.resetConnections(mInterfaceName);
+
+        if (!NetworkUtils.stopDhcp(mInterfaceName)) {
+            Log.e(TAG, "Could not stop DHCP");
+        }
+
+        /* Disable interface */
+        NetworkUtils.disableInterface(mInterfaceName);
+
+        /* send event to CM & network change broadcast */
+        setDetailedState(DetailedState.DISCONNECTED);
+        sendNetworkStateChangeBroadcast(mLastBssid);
+
+        /* Reset data structures */
+        mWifiInfo.setIpAddress(0);
+        mWifiInfo.setBSSID(null);
+        mWifiInfo.setSSID(null);
+        mWifiInfo.setNetworkId(-1);
+
+        /* Clear network properties */
+        mNetworkProperties.clear();
+
+        mLastBssid= null;
+        mLastNetworkId = -1;
+
+    }
+
+
+    /*********************************************************
+     * Notifications from WifiMonitor
+     ********************************************************/
+
+    /**
+     * A structure for supplying information about a supplicant state
+     * change in the STATE_CHANGE event message that comes from the
+     * WifiMonitor
+     * thread.
+     */
+    private static class StateChangeResult {
+        StateChangeResult(int networkId, String BSSID, Object state) {
+            this.state = state;
+            this.BSSID = BSSID;
+            this.networkId = networkId;
+        }
+        int networkId;
+        String BSSID;
+        Object state;
+    }
+
+    /**
+     * Send the tracker a notification that a user-entered password key
+     * may be incorrect (i.e., caused authentication to fail).
+     */
+    void notifyPasswordKeyMayBeIncorrect() {
+        sendMessage(PASSWORD_MAY_BE_INCORRECT_EVENT);
+    }
+
+    /**
+     * Send the tracker a notification that a connection to the supplicant
+     * daemon has been established.
+     */
+    void notifySupplicantConnection() {
+        sendMessage(SUP_CONNECTION_EVENT);
+    }
+
+    /**
+     * Send the tracker a notification that a connection to the supplicant
+     * daemon has been established.
+     */
+    void notifySupplicantLost() {
+        sendMessage(SUP_DISCONNECTION_EVENT);
+    }
+
+    /**
+     * Send the tracker a notification that the state of Wifi connectivity
+     * has changed.
+     * @param networkId the configured network on which the state change occurred
+     * @param newState the new network state
+     * @param BSSID when the new state is {@link DetailedState#CONNECTED
+     * NetworkInfo.DetailedState.CONNECTED},
+     * this is the MAC address of the access point. Otherwise, it
+     * is {@code null}.
+     */
+    void notifyNetworkStateChange(DetailedState newState, String BSSID, int networkId) {
+        if (newState == NetworkInfo.DetailedState.CONNECTED) {
+            sendMessage(obtainMessage(NETWORK_CONNECTION_EVENT,
+                    new StateChangeResult(networkId, BSSID, newState)));
+        } else {
+            sendMessage(obtainMessage(NETWORK_DISCONNECTION_EVENT,
+                    new StateChangeResult(networkId, BSSID, newState)));
+        }
+    }
+
+    /**
+     * Send the tracker a notification that the state of the supplicant
+     * has changed.
+     * @param networkId the configured network on which the state change occurred
+     * @param newState the new {@code SupplicantState}
+     */
+    void notifySupplicantStateChange(int networkId, String BSSID, SupplicantState newState) {
+        sendMessage(obtainMessage(SUPPLICANT_STATE_CHANGE_EVENT,
+                new StateChangeResult(networkId, BSSID, newState)));
+    }
+
+    /**
+     * Send the tracker a notification that a scan has completed, and results
+     * are available.
+     */
+    void notifyScanResultsAvailable() {
+        /**
+         * Switch scan mode over to passive.
+         * Turning off scan-only mode happens only in "Connect" mode
+         */
+        setScanType(false);
+        sendMessage(SCAN_RESULTS_EVENT);
+    }
+
+    void notifyDriverStarted() {
+        sendMessage(DRIVER_START_EVENT);
+    }
+
+    void notifyDriverStopped() {
+        sendMessage(DRIVER_STOP_EVENT);
+    }
+
+    void notifyDriverHung() {
+        setWifiEnabled(false);
+        setWifiEnabled(true);
+    }
+
+
+    /********************************************************
+     * HSM states
+     *******************************************************/
+
+    class DefaultState extends HierarchicalState {
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch (message.what) {
+                    /* Synchronous call returns */
+                case CMD_PING_SUPPLICANT:
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_RECONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_REMOVE_NETWORK:
+                case CMD_ENABLE_NETWORK:
+                case CMD_DISABLE_NETWORK:
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                case CMD_GET_RSSI:
+                case CMD_GET_RSSI_APPROX:
+                case CMD_GET_LINK_SPEED:
+                case CMD_GET_MAC_ADDR:
+                case CMD_SAVE_CONFIG:
+                case CMD_CONNECTION_STATUS:
+                case CMD_GET_NETWORK_CONFIG:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = false;
+                        syncParams.mSyncReturn.intValue = -1;
+                        syncParams.mSyncReturn.stringValue = null;
+                        syncParams.mSyncReturn.configList = null;
+                        notifyOnMsgObject(message);
+                    }
+                    break;
+                case CM_CMD_TEARDOWN:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    mTeardownRequested.set(true);
+                    sendMessage(CMD_DISCONNECT);
+                    sendMessage(CMD_STOP_DRIVER);
+                    /* Mark wifi available when CM tears down */
+                    mNetworkInfo.setIsAvailable(true);
+                    break;
+                case CM_CMD_RECONNECT:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    mTeardownRequested.set(false);
+                    sendMessage(CMD_START_DRIVER);
+                    sendMessage(CMD_RECONNECT);
+                    break;
+                case CMD_ENABLE_RSSI_POLL:
+                    mEnableRssiPolling = (message.arg1 == 1);
+                    mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL);
+                    break;
+                default:
+                    if (DBG) Log.w(TAG, "Unhandled " + message);
+                    break;
+            }
+            return HANDLED;
+        }
+    }
+
+    class InitialState extends HierarchicalState {
+        @Override
+        //TODO: could move logging into a common class
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            // [31-8] Reserved for future use
+            // [7 - 0] HSM state change
+            // 50021 wifi_state_changed (custom|1|5)
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            if (WifiNative.isDriverLoaded()) {
+                transitionTo(mDriverLoadedState);
+            }
+            else {
+                transitionTo(mDriverUnloadedState);
+            }
+        }
+    }
+
+    class DriverLoadingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            final Message message = new Message();
+            message.copyFrom(getCurrentMessage());
+            new Thread(new Runnable() {
+                public void run() {
+                    sWakeLock.acquire();
+                    //enabling state
+                    switch(message.arg1) {
+                        case WIFI_STATE_ENABLING:
+                            setWifiState(WIFI_STATE_ENABLING);
+                            break;
+                        case WIFI_AP_STATE_ENABLING:
+                            setWifiApState(WIFI_AP_STATE_ENABLING);
+                            break;
+                    }
+
+                    if(WifiNative.loadDriver()) {
+                        Log.d(TAG, "Driver load successful");
+                        sendMessage(CMD_LOAD_DRIVER_SUCCESS);
+                    } else {
+                        Log.e(TAG, "Failed to load driver!");
+                        switch(message.arg1) {
+                            case WIFI_STATE_ENABLING:
+                                setWifiState(WIFI_STATE_UNKNOWN);
+                                break;
+                            case WIFI_AP_STATE_ENABLING:
+                                setWifiApState(WIFI_AP_STATE_FAILED);
+                                break;
+                        }
+                        sendMessage(CMD_LOAD_DRIVER_FAILURE);
+                    }
+                    sWakeLock.release();
+                }
+            }).start();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_LOAD_DRIVER_SUCCESS:
+                    transitionTo(mDriverLoadedState);
+                    break;
+                case CMD_LOAD_DRIVER_FAILURE:
+                    transitionTo(mDriverFailedState);
+                    break;
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverLoadedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case CMD_UNLOAD_DRIVER:
+                    transitionTo(mDriverUnloadingState);
+                    break;
+                case CMD_START_SUPPLICANT:
+                    if(WifiNative.startSupplicant()) {
+                        Log.d(TAG, "Supplicant start successful");
+                        mWifiMonitor.startMonitoring();
+                        setWifiState(WIFI_STATE_ENABLED);
+                        transitionTo(mWaitForSupState);
+                    } else {
+                        Log.e(TAG, "Failed to start supplicant!");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0));
+                    }
+                    break;
+                case CMD_START_AP:
+                    try {
+                        nwService.startAccessPoint((WifiConfiguration) message.obj,
+                                    mInterfaceName,
+                                    SOFTAP_IFACE);
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in startAccessPoint()");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0));
+                        break;
+                    }
+                    Log.d(TAG, "Soft AP start successful");
+                    setWifiApState(WIFI_AP_STATE_ENABLED);
+                    transitionTo(mSoftApStartedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverUnloadingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            final Message message = new Message();
+            message.copyFrom(getCurrentMessage());
+            new Thread(new Runnable() {
+                public void run() {
+                    if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+                    sWakeLock.acquire();
+                    if(WifiNative.unloadDriver()) {
+                        Log.d(TAG, "Driver unload successful");
+                        sendMessage(CMD_UNLOAD_DRIVER_SUCCESS);
+
+                        switch(message.arg1) {
+                            case WIFI_STATE_DISABLED:
+                            case WIFI_STATE_UNKNOWN:
+                                setWifiState(message.arg1);
+                                break;
+                            case WIFI_AP_STATE_DISABLED:
+                            case WIFI_AP_STATE_FAILED:
+                                setWifiApState(message.arg1);
+                                break;
+                        }
+                    } else {
+                        Log.e(TAG, "Failed to unload driver!");
+                        sendMessage(CMD_UNLOAD_DRIVER_FAILURE);
+
+                        switch(message.arg1) {
+                            case WIFI_STATE_DISABLED:
+                            case WIFI_STATE_UNKNOWN:
+                                setWifiState(WIFI_STATE_UNKNOWN);
+                                break;
+                            case WIFI_AP_STATE_DISABLED:
+                            case WIFI_AP_STATE_FAILED:
+                                setWifiApState(WIFI_AP_STATE_FAILED);
+                                break;
+                        }
+                    }
+                    sWakeLock.release();
+                }
+            }).start();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_UNLOAD_DRIVER_SUCCESS:
+                    transitionTo(mDriverUnloadedState);
+                    break;
+                case CMD_UNLOAD_DRIVER_FAILURE:
+                    transitionTo(mDriverFailedState);
+                    break;
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverUnloadedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_LOAD_DRIVER:
+                    transitionTo(mDriverLoadingState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverFailedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            Log.e(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            return NOT_HANDLED;
+        }
+    }
+
+
+    class WaitForSupState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case SUP_CONNECTION_EVENT:
+                    Log.d(TAG, "Supplicant connection established");
+                    mSupplicantStateTracker.resetSupplicantState();
+                    /* Initialize data structures */
+                    resetNotificationTimer();
+                    setTeardownRequested(false);
+                    mLastBssid = null;
+                    mLastNetworkId = -1;
+                    mLastSignalLevel = -1;
+
+                    mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand());
+
+                    //TODO: initialize and fix multicast filtering
+                    //mWM.initializeMulticastFiltering();
+
+                    if (mBluetoothA2dp == null) {
+                        mBluetoothA2dp = new BluetoothA2dp(mContext);
+                    }
+                    checkIsBluetoothPlaying();
+
+                    checkUseStaticIp();
+                    sendSupplicantConnectionChangedBroadcast(true);
+                    transitionTo(mDriverSupReadyState);
+                    break;
+                case CMD_STOP_SUPPLICANT:
+                    Log.d(TAG, "Stop supplicant received");
+                    WifiNative.stopSupplicant();
+                    transitionTo(mDriverLoadedState);
+                    break;
+                    /* Fail soft ap when waiting for supplicant start */
+                case CMD_START_AP:
+                    Log.d(TAG, "Failed to start soft AP with a running supplicant");
+                    setWifiApState(WIFI_AP_STATE_FAILED);
+                    break;
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                case CMD_STOP_AP:
+                case CMD_START_SUPPLICANT:
+                case CMD_UNLOAD_DRIVER:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverSupReadyState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+            /* Initialize for connect mode operation at start */
+            mIsScanMode = false;
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_STOP_SUPPLICANT:   /* Supplicant stopped by user */
+                    Log.d(TAG, "Stop supplicant received");
+                    WifiNative.stopSupplicant();
+                    //$FALL-THROUGH$
+                case SUP_DISCONNECTION_EVENT:  /* Supplicant died */
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    //Remove any notifications on disconnection
+                    setNotificationVisible(false, 0, false, 0);
+                    WifiNative.closeSupplicantConnection();
+                    handleNetworkDisconnect();
+                    sendSupplicantConnectionChangedBroadcast(false);
+                    mSupplicantStateTracker.resetSupplicantState();
+                    transitionTo(mDriverLoadedState);
+
+                    /* When supplicant dies, unload driver and enter failed state */
+                    //TODO: consider bringing up supplicant again
+                    if (message.what == SUP_DISCONNECTION_EVENT) {
+                        Log.d(TAG, "Supplicant died, unloading driver");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0));
+                    }
+                    break;
+                case CMD_START_DRIVER:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    WifiNative.startDriverCommand();
+                    transitionTo(mDriverStartingState);
+                    break;
+                case SCAN_RESULTS_EVENT:
+                    setScanResults(WifiNative.scanResultsCommand());
+                    sendScanResultsAvailableBroadcast();
+                    checkAndSetNotification();
+                    break;
+                case CMD_PING_SUPPLICANT:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = WifiNative.pingCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    syncParams = (SyncParams) message.obj;
+                    WifiConfiguration config = (WifiConfiguration) syncParams.mParameter;
+                    syncParams.mSyncReturn.intValue = addOrUpdateNetworkNative(config);
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_REMOVE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.removeNetworkCommand(
+                                message.arg1);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.removeNetworkCommand(message.arg1);
+                    }
+                    break;
+                case CMD_ENABLE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        EnableNetParams enableNetParams = (EnableNetParams) syncParams.mParameter;
+                        syncParams.mSyncReturn.boolValue = WifiNative.enableNetworkCommand(
+                                enableNetParams.netId, enableNetParams.disableOthers);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.enableNetworkCommand(message.arg1, message.arg2 == 1);
+                    }
+                    break;
+                case CMD_DISABLE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.disableNetworkCommand(
+                                message.arg1);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.disableNetworkCommand(message.arg1);
+                    }
+                    break;
+                case CMD_BLACKLIST_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    WifiNative.addToBlacklistCommand((String)message.obj);
+                    break;
+                case CMD_CLEAR_BLACKLIST:
+                    WifiNative.clearBlacklistCommand();
+                    break;
+                case CMD_GET_NETWORK_CONFIG:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.configList = getConfiguredNetworksNative();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_SAVE_CONFIG:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.saveConfigCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.saveConfigCommand();
+                    }
+                    // Inform the backup manager about a data change
+                    IBackupManager ibm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    if (ibm != null) {
+                        try {
+                            ibm.dataChanged("com.android.providers.settings");
+                        } catch (Exception e) {
+                            // Try again later
+                        }
+                    }
+                    break;
+                case CMD_CONNECTION_STATUS:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.stringValue = WifiNative.statusCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_GET_MAC_ADDR:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.stringValue = WifiNative.getMacAddressCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                    /* Cannot start soft AP while in client mode */
+                case CMD_START_AP:
+                    Log.d(TAG, "Failed to start soft AP with a running supplicant");
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    setWifiApState(WIFI_AP_STATE_FAILED);
+                    break;
+                case CMD_SET_SCAN_MODE:
+                    mIsScanMode = (message.arg1 == SCAN_ONLY_MODE);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class DriverStartingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case DRIVER_START_EVENT:
+                    transitionTo(mDriverStartedState);
+                    break;
+                    /* Queue driver commands & connection events */
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                case NETWORK_CONNECTION_EVENT:
+                case NETWORK_DISCONNECTION_EVENT:
+                case PASSWORD_MAY_BE_INCORRECT_EVENT:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                    /* Queue the asynchronous version of these commands */
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONNECT:
+                    if (message.arg2 != SYNCHRONOUS_CALL) {
+                        deferMessage(message);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverStartedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            try {
+                mBatteryStats.noteWifiRunning();
+            } catch (RemoteException ignore) {}
+
+            /* Initialize channel count */
+            setNumAllowedChannels();
+
+            if (mIsScanMode) {
+                WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE);
+                WifiNative.disconnectCommand();
+                transitionTo(mScanModeState);
+            } else {
+                WifiNative.setScanResultHandlingCommand(CONNECT_MODE);
+                WifiNative.reconnectCommand();
+                transitionTo(mConnectModeState);
+            }
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_SET_SCAN_TYPE:
+                    if (message.arg1 == SCAN_ACTIVE) {
+                        WifiNative.setScanModeCommand(true);
+                    } else {
+                        WifiNative.setScanModeCommand(false);
+                    }
+                    break;
+                case CMD_SET_POWER_MODE:
+                    WifiNative.setPowerModeCommand(message.arg1);
+                    break;
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                    WifiNative.setBluetoothCoexistenceModeCommand(message.arg1);
+                    break;
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                    WifiNative.setBluetoothCoexistenceScanModeCommand(message.arg1 == 1);
+                    break;
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                    mNumAllowedChannels = message.arg1;
+                    WifiNative.setNumAllowedChannelsCommand(message.arg1);
+                    break;
+                case CMD_START_DRIVER:
+                    /* Ignore another driver start */
+                    break;
+                case CMD_STOP_DRIVER:
+                    WifiNative.stopDriverCommand();
+                    transitionTo(mDriverStoppingState);
+                    break;
+                case CMD_REQUEST_CM_WAKELOCK:
+                    if (mCm == null) {
+                        mCm = (ConnectivityManager)mContext.getSystemService(
+                                Context.CONNECTIVITY_SERVICE);
+                    }
+                    mCm.requestNetworkTransitionWakelock(TAG);
+                    break;
+                case CMD_START_PACKET_FILTERING:
+                    WifiNative.startPacketFiltering();
+                    break;
+                case CMD_STOP_PACKET_FILTERING:
+                    WifiNative.stopPacketFiltering();
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+        @Override
+        public void exit() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            try {
+                mBatteryStats.noteWifiStopped();
+            } catch (RemoteException ignore) { }
+        }
+    }
+
+    class DriverStoppingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case DRIVER_STOP_EVENT:
+                    transitionTo(mDriverStoppedState);
+                    break;
+                    /* Queue driver commands */
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                    /* Queue the asynchronous version of these commands */
+                case CMD_START_SCAN:
+                case CMD_DISCONNECT:
+                case CMD_REASSOCIATE:
+                case CMD_RECONNECT:
+                    if (message.arg2 != SYNCHRONOUS_CALL) {
+                        deferMessage(message);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverStoppedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            // Take down any open network notifications on driver stop
+            setNotificationVisible(false, 0, false, 0);
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            return NOT_HANDLED;
+        }
+    }
+
+    class ScanModeState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        /* Ignore */
+                        return HANDLED;
+                    } else {
+                        WifiNative.setScanResultHandlingCommand(message.arg1);
+                        WifiNative.reconnectCommand();
+                        mIsScanMode = false;
+                        transitionTo(mDisconnectedState);
+                    }
+                    break;
+                case CMD_START_SCAN:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.scanCommand(
+                                message.arg1 == SCAN_ACTIVE);
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                    }
+                    break;
+                    /* Ignore */
+                case CMD_DISCONNECT:
+                case CMD_RECONNECT:
+                case CMD_REASSOCIATE:
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                case NETWORK_CONNECTION_EVENT:
+                case NETWORK_DISCONNECTION_EVENT:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class ConnectModeState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            StateChangeResult stateChangeResult;
+            switch(message.what) {
+                case PASSWORD_MAY_BE_INCORRECT_EVENT:
+                    mPasswordKeyMayBeIncorrect = true;
+                    break;
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                    stateChangeResult = (StateChangeResult) message.obj;
+                    mSupplicantStateTracker.handleEvent(stateChangeResult);
+                    break;
+                case CMD_START_SCAN:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = true;
+                        notifyOnMsgObject(message);
+                    }
+                    /* We need to set scan type in completed state */
+                    Message newMsg = obtainMessage();
+                    newMsg.copyFrom(message);
+                    mSupplicantStateTracker.sendMessage(newMsg);
+                    break;
+                    /* Do a redundant disconnect without transition */
+                case CMD_DISCONNECT:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.disconnectCommand();
+                    }
+                    break;
+                case CMD_RECONNECT:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.reconnectCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.reconnectCommand();
+                    }
+                    break;
+                case CMD_REASSOCIATE:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.reassociateCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.reassociateCommand();
+                    }
+                    break;
+                case SCAN_RESULTS_EVENT:
+                    /* Set the scan setting back to "connect" mode */
+                    WifiNative.setScanResultHandlingCommand(CONNECT_MODE);
+                    /* Handle scan results */
+                    return NOT_HANDLED;
+                case NETWORK_CONNECTION_EVENT:
+                    Log.d(TAG,"Network connection established");
+                    stateChangeResult = (StateChangeResult) message.obj;
+
+                    /* Remove any notifications */
+                    setNotificationVisible(false, 0, false, 0);
+                    resetNotificationTimer();
+
+                    mWifiInfo.setBSSID(mLastBssid = stateChangeResult.BSSID);
+                    mWifiInfo.setNetworkId(stateChangeResult.networkId);
+                    mLastNetworkId = stateChangeResult.networkId;
+
+                    /* send event to CM & network change broadcast */
+                    setDetailedState(DetailedState.OBTAINING_IPADDR);
+                    sendNetworkStateChangeBroadcast(mLastBssid);
+
+                    transitionTo(mConnectingState);
+                    break;
+                case NETWORK_DISCONNECTION_EVENT:
+                    Log.d(TAG,"Network connection lost");
+                    handleNetworkDisconnect();
+                    transitionTo(mDisconnectedState);
+                    break;
+                case CMD_GET_RSSI:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.intValue = WifiNative.getRssiCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_GET_RSSI_APPROX:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.intValue = WifiNative.getRssiApproxCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_GET_LINK_SPEED:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.intValue = WifiNative.getLinkSpeedCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class ConnectingState extends HierarchicalState {
+        boolean modifiedBluetoothCoexistenceMode;
+        int powerMode;
+        Thread mDhcpThread;
+
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            if (!mUseStaticIp) {
+
+                mDhcpThread = null;
+                modifiedBluetoothCoexistenceMode = false;
+                powerMode = DRIVER_POWER_MODE_AUTO;
+
+                if (shouldDisableCoexistenceMode()) {
+                    /*
+                     * There are problems setting the Wi-Fi driver's power
+                     * mode to active when bluetooth coexistence mode is
+                     * enabled or sense.
+                     * <p>
+                     * We set Wi-Fi to active mode when
+                     * obtaining an IP address because we've found
+                     * compatibility issues with some routers with low power
+                     * mode.
+                     * <p>
+                     * In order for this active power mode to properly be set,
+                     * we disable coexistence mode until we're done with
+                     * obtaining an IP address.  One exception is if we
+                     * are currently connected to a headset, since disabling
+                     * coexistence would interrupt that connection.
+                     */
+                    modifiedBluetoothCoexistenceMode = true;
+
+                    // Disable the coexistence mode
+                    WifiNative.setBluetoothCoexistenceModeCommand(
+                            WifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
+                }
+
+                powerMode =  WifiNative.getPowerModeCommand();
+                if (powerMode < 0) {
+                  // Handle the case where supplicant driver does not support
+                  // getPowerModeCommand.
+                    powerMode = DRIVER_POWER_MODE_AUTO;
+                }
+                if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
+                    WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE);
+                }
+
+                Log.d(TAG, "DHCP request started");
+                mDhcpThread = new Thread(new Runnable() {
+                    public void run() {
+                        if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {
+                            Log.d(TAG, "DHCP request succeeded");
+                            sendMessage(CMD_IP_CONFIG_SUCCESS);
+                        } else {
+                            Log.d(TAG, "DHCP request failed: " +
+                                    NetworkUtils.getDhcpError());
+                            sendMessage(CMD_IP_CONFIG_FAILURE);
+                        }
+                    }
+                });
+                mDhcpThread.start();
+            } else {
+                if (NetworkUtils.configureInterface(mInterfaceName, mDhcpInfo)) {
+                    Log.v(TAG, "Static IP configuration succeeded");
+                    sendMessage(CMD_IP_CONFIG_SUCCESS);
+                } else {
+                    Log.v(TAG, "Static IP configuration failed");
+                    sendMessage(CMD_IP_CONFIG_FAILURE);
+                }
+            }
+         }
+      @Override
+      public boolean processMessage(Message message) {
+          if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+
+          switch(message.what) {
+              case CMD_IP_CONFIG_SUCCESS:
+                  mReconnectCount = 0;
+                  mLastSignalLevel = -1; // force update of signal strength
+                  mWifiInfo.setIpAddress(mDhcpInfo.ipAddress);
+                  Log.d(TAG, "IP configuration: " + mDhcpInfo);
+                  configureNetworkProperties();
+                  setDetailedState(DetailedState.CONNECTED);
+                  sendNetworkStateChangeBroadcast(mLastBssid);
+                  transitionTo(mConnectedState);
+                  break;
+              case CMD_IP_CONFIG_FAILURE:
+                  mWifiInfo.setIpAddress(0);
+
+                  Log.e(TAG, "IP configuration failed");
+                  /**
+                   * If we've exceeded the maximum number of retries for DHCP
+                   * to a given network, disable the network
+                   */
+                  if (++mReconnectCount > getMaxDhcpRetries()) {
+                          Log.e(TAG, "Failed " +
+                                  mReconnectCount + " times, Disabling " + mLastNetworkId);
+                      WifiNative.disableNetworkCommand(mLastNetworkId);
+                  }
+
+                  /* DHCP times out after about 30 seconds, we do a
+                   * disconnect and an immediate reconnect to try again
+                   */
+                  WifiNative.disconnectCommand();
+                  WifiNative.reconnectCommand();
+                  transitionTo(mDisconnectingState);
+                  break;
+              case CMD_DISCONNECT:
+                  if (message.arg2 == SYNCHRONOUS_CALL) {
+                      SyncParams syncParams = (SyncParams) message.obj;
+                      syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand();
+                      notifyOnMsgObject(message);
+                  } else {
+                      /* asynchronous handling */
+                      WifiNative.disconnectCommand();
+                  }
+                  transitionTo(mDisconnectingState);
+                  break;
+                  /* Ignore */
+              case NETWORK_CONNECTION_EVENT:
+                  break;
+              case CMD_STOP_DRIVER:
+                  sendMessage(CMD_DISCONNECT);
+                  deferMessage(message);
+                  break;
+              case CMD_SET_SCAN_MODE:
+                  if (message.arg1 == SCAN_ONLY_MODE) {
+                      sendMessage(CMD_DISCONNECT);
+                      deferMessage(message);
+                  }
+                  break;
+              case CMD_RECONFIGURE_IP:
+                  deferMessage(message);
+                  break;
+              default:
+                return NOT_HANDLED;
+          }
+          EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+          return HANDLED;
+      }
+
+      @Override
+      public void exit() {
+          /* reset power state & bluetooth coexistence if on DHCP */
+          if (!mUseStaticIp) {
+              if (powerMode != DRIVER_POWER_MODE_ACTIVE) {
+                  WifiNative.setPowerModeCommand(powerMode);
+              }
+
+              if (modifiedBluetoothCoexistenceMode) {
+                  // Set the coexistence mode back to its default value
+                  WifiNative.setBluetoothCoexistenceModeCommand(
+                          WifiNative.BLUETOOTH_COEXISTENCE_MODE_SENSE);
+              }
+          }
+
+      }
+    }
+
+    class ConnectedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_DISCONNECT:
+                    if (message.arg2 == SYNCHRONOUS_CALL) {
+                        SyncParams syncParams = (SyncParams) message.obj;
+                        syncParams.mSyncReturn.boolValue = WifiNative.disconnectCommand();
+                        notifyOnMsgObject(message);
+                    } else {
+                        /* asynchronous handling */
+                        WifiNative.disconnectCommand();
+                    }
+                    transitionTo(mDisconnectingState);
+                    break;
+                case CMD_RECONFIGURE_IP:
+                    Log.d(TAG,"Reconfiguring IP on connection");
+                    NetworkUtils.resetConnections(mInterfaceName);
+                    transitionTo(mConnectingState);
+                    break;
+                case CMD_STOP_DRIVER:
+                    sendMessage(CMD_DISCONNECT);
+                    deferMessage(message);
+                    break;
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        sendMessage(CMD_DISCONNECT);
+                        deferMessage(message);
+                    }
+                    break;
+                    /* Ignore */
+                case NETWORK_CONNECTION_EVENT:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DisconnectingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_STOP_DRIVER: /* Stop driver only after disconnect handled */
+                    deferMessage(message);
+                    break;
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        deferMessage(message);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DisconnectedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_SET_SCAN_MODE:
+                    if (message.arg1 == SCAN_ONLY_MODE) {
+                        WifiNative.setScanResultHandlingCommand(message.arg1);
+                        //Supplicant disconnect to prevent further connects
+                        WifiNative.disconnectCommand();
+                        mIsScanMode = true;
+                        transitionTo(mScanModeState);
+                    }
+                    break;
+                    /* Ignore network disconnect */
+                case NETWORK_DISCONNECTION_EVENT:
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class SoftApStartedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case CMD_STOP_AP:
+                    Log.d(TAG,"Stopping Soft AP");
+                    setWifiApState(WIFI_AP_STATE_DISABLING);
+                    try {
+                        nwService.stopAccessPoint();
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in stopAccessPoint()");
+                    }
+                    transitionTo(mDriverLoadedState);
+                    break;
+                case CMD_START_AP:
+                    Log.d(TAG,"SoftAP set on a running access point");
+                    try {
+                        nwService.setAccessPoint((WifiConfiguration) message.obj,
+                                    mInterfaceName,
+                                    SOFTAP_IFACE);
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in nwService during soft AP set");
+                        try {
+                            nwService.stopAccessPoint();
+                        } catch (Exception ee) {
+                            Slog.e(TAG, "Could not stop AP, :" + ee);
+                        }
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0));
+                    }
+                    break;
+                /* Fail client mode operation when soft AP is enabled */
+                case CMD_START_SUPPLICANT:
+                    Log.e(TAG,"Cannot start supplicant with a running soft AP");
+                    setWifiState(WIFI_STATE_UNKNOWN);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+
+    class SupplicantStateTracker extends HierarchicalStateMachine {
+
+        private int mRssiPollToken = 0;
+
+        /**
+         * The max number of the WPA supplicant loop iterations before we
+         * decide that the loop should be terminated:
+         */
+        private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+        private int mLoopDetectIndex = 0;
+        private int mLoopDetectCount = 0;
+
+        /**
+         *  Supplicant state change commands follow
+         *  the ordinal values defined in SupplicantState.java
+         */
+        private static final int DISCONNECTED           = 0;
+        private static final int INACTIVE               = 1;
+        private static final int SCANNING               = 2;
+        private static final int ASSOCIATING            = 3;
+        private static final int ASSOCIATED             = 4;
+        private static final int FOUR_WAY_HANDSHAKE     = 5;
+        private static final int GROUP_HANDSHAKE        = 6;
+        private static final int COMPLETED              = 7;
+        private static final int DORMANT                = 8;
+        private static final int UNINITIALIZED          = 9;
+        private static final int INVALID                = 10;
+
+        private HierarchicalState mUninitializedState;
+        private HierarchicalState mInitializedState;
+        private HierarchicalState mInactiveState;
+        private HierarchicalState mDisconnectState;
+        private HierarchicalState mScanState;
+        private HierarchicalState mConnectState;
+        private HierarchicalState mHandshakeState;
+        private HierarchicalState mCompletedState;
+        private HierarchicalState mDormantState;
+
+
+        public SupplicantStateTracker(Context context, Handler target) {
+            super(TAG, target.getLooper());
+
+            mUninitializedState = new UninitializedState();
+            mInitializedState = new InitializedState();
+            mInactiveState = new InactiveState();
+            mDisconnectState = new DisconnectedState();
+            mScanState = new ScanState();
+            mConnectState = new ConnectState();
+            mHandshakeState = new HandshakeState();
+            mCompletedState = new CompletedState();
+            mDormantState = new DormantState();
+
+
+            addState(mUninitializedState);
+            addState(mInitializedState);
+                addState(mInactiveState, mInitializedState);
+                addState(mDisconnectState, mInitializedState);
+                addState(mScanState, mInitializedState);
+                addState(mConnectState, mInitializedState);
+                    addState(mHandshakeState, mConnectState);
+                    addState(mCompletedState, mConnectState);
+                addState(mDormantState, mInitializedState);
+
+            setInitialState(mUninitializedState);
+
+            //start the state machine
+            start();
+        }
+
+        public void handleEvent(StateChangeResult stateChangeResult) {
+            SupplicantState newState = (SupplicantState) stateChangeResult.state;
+
+            // Supplicant state change
+            // [31-13] Reserved for future use
+            // [8 - 0] Supplicant state (as defined in SupplicantState.java)
+            // 50023 supplicant_state_changed (custom|1|5)
+            EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, newState.ordinal());
+
+            sendMessage(obtainMessage(newState.ordinal(), stateChangeResult));
+        }
+
+        public void resetSupplicantState() {
+            transitionTo(mUninitializedState);
+        }
+
+        private void resetLoopDetection() {
+            mLoopDetectCount = 0;
+            mLoopDetectIndex = 0;
+        }
+
+        private boolean handleTransition(Message msg) {
+            if (DBG) Log.d(TAG, getName() + msg.toString() + "\n");
+            switch (msg.what) {
+                case DISCONNECTED:
+                    transitionTo(mDisconnectState);
+                    break;
+                case SCANNING:
+                    transitionTo(mScanState);
+                    break;
+                case ASSOCIATING:
+                    StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+                    /* BSSID is valid only in ASSOCIATING state */
+                    mWifiInfo.setBSSID(stateChangeResult.BSSID);
+                    //$FALL-THROUGH$
+                case ASSOCIATED:
+                case FOUR_WAY_HANDSHAKE:
+                case GROUP_HANDSHAKE:
+                    transitionTo(mHandshakeState);
+                    break;
+                case COMPLETED:
+                    transitionTo(mCompletedState);
+                    break;
+                case DORMANT:
+                    transitionTo(mDormantState);
+                    break;
+                case INACTIVE:
+                    transitionTo(mInactiveState);
+                    break;
+                case UNINITIALIZED:
+                case INVALID:
+                    transitionTo(mUninitializedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+            SupplicantState supState = (SupplicantState) stateChangeResult.state;
+            setDetailedState(WifiInfo.getDetailedStateOf(supState));
+            mWifiInfo.setSupplicantState(supState);
+            mWifiInfo.setNetworkId(stateChangeResult.networkId);
+            //TODO: Modify WifiMonitor to report SSID on events
+            //mWifiInfo.setSSID()
+            return HANDLED;
+        }
+
+        /********************************************************
+         * HSM states
+         *******************************************************/
+
+        class InitializedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+                switch (message.what) {
+                    case CMD_START_SCAN:
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                        break;
+                    default:
+                        if (DBG) Log.w(TAG, "Ignoring " + message);
+                        break;
+                }
+                return HANDLED;
+            }
+        }
+
+        class UninitializedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 mNetworkInfo.setIsAvailable(false);
+                 resetLoopDetection();
+                 mPasswordKeyMayBeIncorrect = false;
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    default:
+                        if (!handleTransition(message)) {
+                            if (DBG) Log.w(TAG, "Ignoring " + message);
+                        }
+                        break;
+                }
+                return HANDLED;
+            }
+        }
+
+        class InactiveState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(false);
+                 resetLoopDetection();
+                 mPasswordKeyMayBeIncorrect = false;
+
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+
+        class DisconnectedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+                 resetLoopDetection();
+
+                 /* If a disconnect event happens after a password key failure
+                  * event, disable the network
+                  */
+                 if (mPasswordKeyMayBeIncorrect) {
+                     Log.d(TAG, "Failed to authenticate, disabling network " +
+                             mWifiInfo.getNetworkId());
+                     WifiNative.disableNetworkCommand(mWifiInfo.getNetworkId());
+                     mPasswordKeyMayBeIncorrect = false;
+                     sendSupplicantStateChangedBroadcast(stateChangeResult, true);
+                 }
+                 else {
+                     sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+                 }
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class ScanState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+                 mPasswordKeyMayBeIncorrect = false;
+                 resetLoopDetection();
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class ConnectState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_START_SCAN:
+                        WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE);
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class HandshakeState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 final Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+
+                 if (mLoopDetectIndex > message.what) {
+                     mLoopDetectCount++;
+                 }
+                 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+                     WifiNative.disableNetworkCommand(stateChangeResult.networkId);
+                     mLoopDetectCount = 0;
+                 }
+
+                 mLoopDetectIndex = message.what;
+
+                 mPasswordKeyMayBeIncorrect = false;
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class CompletedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+
+                 mRssiPollToken++;
+                 if (mEnableRssiPolling) {
+                     sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                             POLL_RSSI_INTERVAL_MSECS);
+                 }
+
+                 resetLoopDetection();
+
+                 mPasswordKeyMayBeIncorrect = false;
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    case ASSOCIATING:
+                    case ASSOCIATED:
+                    case FOUR_WAY_HANDSHAKE:
+                    case GROUP_HANDSHAKE:
+                    case COMPLETED:
+                        break;
+                    case CMD_RSSI_POLL:
+                        if (message.arg1 == mRssiPollToken) {
+                            // Get Info and continue polling
+                            requestPolledInfo();
+                            sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                    POLL_RSSI_INTERVAL_MSECS);
+                        } else {
+                            // Polling has completed
+                        }
+                        break;
+                    case CMD_ENABLE_RSSI_POLL:
+                        mRssiPollToken++;
+                        if (mEnableRssiPolling) {
+                            // first poll
+                            requestPolledInfo();
+                            sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                    POLL_RSSI_INTERVAL_MSECS);
+                        }
+                        break;
+                    default:
+                        return handleTransition(message);
+                }
+                return HANDLED;
+            }
+        }
+
+        class DormantState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 mNetworkInfo.setIsAvailable(true);
+                 resetLoopDetection();
+                 mPasswordKeyMayBeIncorrect = false;
+
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+
+                 /* TODO: reconnect is now being handled at DHCP failure handling
+                  * If we run into issues with staying in Dormant state, might
+                  * need a reconnect here
+                  */
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+    }
+
     private class NotificationEnabledSettingObserver extends ContentObserver {
 
         public NotificationEnabledSettingObserver(Handler handler) {
@@ -2627,8 +3964,4 @@
                     Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
         }
     }
-
-    public NetworkProperties getNetworkProperties() {
-        return mNetworkProperties;
-    }
 }