Merge "Adding quaternion functions to math library."
diff --git a/Android.mk b/Android.mk
index 7c29c73..524ec51 100644
--- a/Android.mk
+++ b/Android.mk
@@ -368,7 +368,7 @@
     -since ./frameworks/base/api/7.xml 7 \
     -since ./frameworks/base/api/8.xml 8 \
     -since ./frameworks/base/api/current.xml HC \
-		-error 1 -error 2 -warning 3 -error 4 -error 6 -error 8 \
+		-error 101 -error 102 -warning 103 -error 104 -error 106 -error 108 \
 		-overview $(LOCAL_PATH)/core/java/overview.html
 
 framework_docs_LOCAL_ADDITIONAL_JAVA_DIR:=$(call intermediates-dir-for,JAVA_LIBRARIES,framework)
diff --git a/api/current.xml b/api/current.xml
index 61c1cac..a9ef54e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -26353,6 +26353,17 @@
 <parameter name="id" type="int">
 </parameter>
 </method>
+<method name="getActionBar"
+ return="android.app.ActionBar"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getContext"
  return="android.content.Context"
  abstract="false"
@@ -30658,6 +30669,17 @@
 <parameter name="holder" type="android.view.SurfaceHolder">
 </parameter>
 </method>
+<field name="KEY_NATIVE_SAVED_STATE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android:native_state&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="META_DATA_LIB_NAME"
  type="java.lang.String"
  transient="false"
@@ -61912,6 +61934,21 @@
 <parameter name="sqlString" type="java.lang.String">
 </parameter>
 </method>
+<method name="appendSelectionArgs"
+ return="java.lang.String[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="originalValues" type="java.lang.String[]">
+</parameter>
+<parameter name="newValues" type="java.lang.String[]">
+</parameter>
+</method>
 <method name="appendValueToSql"
  return="void"
  abstract="false"
@@ -91791,7 +91828,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="1"
+ value="0"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -91802,7 +91839,7 @@
  type="int"
  transient="false"
  volatile="false"
- value="0"
+ value="1"
  static="true"
  final="true"
  deprecated="not deprecated"
@@ -104563,6 +104600,21 @@
  visibility="public"
 >
 </method>
+<method name="getBooleanQueryParameter"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="key" type="java.lang.String">
+</parameter>
+<parameter name="defaultValue" type="boolean">
+</parameter>
+</method>
 <method name="getEncodedAuthority"
  return="java.lang.String"
  abstract="true"
@@ -193670,6 +193722,28 @@
  visibility="public"
 >
 </method>
+<method name="getRotationX"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getRotationY"
+ return="float"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getScaleX"
  return="float"
  abstract="false"
@@ -195840,6 +195914,32 @@
 <parameter name="rotation" type="float">
 </parameter>
 </method>
+<method name="setRotationX"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rotationX" type="float">
+</parameter>
+</method>
+<method name="setRotationY"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="rotationY" type="float">
+</parameter>
+</method>
 <method name="setSaveEnabled"
  return="void"
  abstract="false"
@@ -213236,17 +213336,6 @@
  visibility="public"
 >
 </method>
-<method name="getVisibleTitleHeight"
- return="int"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getZoomControls"
  return="android.view.View"
  abstract="false"
@@ -216570,7 +216659,7 @@
 </interface>
 <class name="AdapterViewAnimator"
  extends="android.widget.AdapterView"
- abstract="false"
+ abstract="true"
  static="false"
  final="false"
  deprecated="not deprecated"
@@ -216620,28 +216709,6 @@
  visibility="public"
 >
 </method>
-<method name="getDefaultInAnimation"
- return="android.view.animation.Animation"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="getDefaultOutAnimation"
- return="android.view.animation.Animation"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
 <method name="getDisplayedChild"
  return="int"
  abstract="false"
@@ -216840,23 +216907,6 @@
  visibility="public"
 >
 </method>
-<method name="showOnly"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="protected"
->
-<parameter name="childIndex" type="int">
-</parameter>
-<parameter name="animate" type="boolean">
-</parameter>
-<parameter name="onLayout" type="boolean">
-</parameter>
-</method>
 <method name="showPrevious"
  return="void"
  abstract="false"
@@ -228032,6 +228082,22 @@
 <parameter name="autoRequery" type="boolean">
 </parameter>
 </constructor>
+<constructor name="ResourceCursorAdapter"
+ type="android.widget.ResourceCursorAdapter"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="layout" type="int">
+</parameter>
+<parameter name="c" type="android.database.Cursor">
+</parameter>
+<parameter name="flags" type="int">
+</parameter>
+</constructor>
 <method name="newView"
  return="android.view.View"
  abstract="false"
@@ -230162,6 +230228,37 @@
 </parameter>
 </method>
 </interface>
+<class name="StackView"
+ extends="android.widget.AdapterViewAnimator"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="StackView"
+ type="android.widget.StackView"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="StackView"
+ type="android.widget.StackView"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="attrs" type="android.util.AttributeSet">
+</parameter>
+</constructor>
+</class>
 <class name="TabHost"
  extends="android.widget.FrameLayout"
  abstract="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d49adc2..3318bb1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1748,13 +1748,11 @@
     
     /**
      * Retrieve a reference to this activity's ActionBar.
-     * 
-     * <p><em>Note:</em> The ActionBar is initialized when a content view
-     * is set. This function will return null if called before {@link #setContentView}
-     * or {@link #addContentView}.
+     *
      * @return The Activity's ActionBar, or null if it does not have one.
      */
     public ActionBar getActionBar() {
+        initActionBar();
         return mActionBar;
     }
     
@@ -1764,7 +1762,7 @@
      */
     private void initActionBar() {
         Window window = getWindow();
-        if (!window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
+        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
             return;
         }
         
@@ -4093,6 +4091,7 @@
     }
 
     public ActionMode onStartActionMode(ActionMode.Callback callback) {
+        initActionBar();
         if (mActionBar != null) {
             return mActionBar.startActionMode(callback);
         }
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index a9420b4e..b4c138e 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import com.android.internal.app.ActionBarImpl;
 import com.android.internal.policy.PolicyManager;
 
 import android.content.ComponentName;
@@ -27,9 +28,9 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.view.ActionMode;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.ActionMode;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -77,6 +78,7 @@
     final WindowManager mWindowManager;
     Window mWindow;
     View mDecor;
+    private ActionBarImpl mActionBar;
     /**
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -178,6 +180,15 @@
     }
 
     /**
+     * Retrieve the {@link ActionBar} attached to this dialog, if present.
+     *
+     * @return The ActionBar attached to the dialog or null if no ActionBar is present.
+     */
+    public ActionBar getActionBar() {
+        return mActionBar;
+    }
+
+    /**
      * Sets the Activity that owns this dialog. An example use: This Dialog will
      * use the suggested volume control stream of the Activity.
      * 
@@ -228,6 +239,11 @@
 
         onStart();
         mDecor = mWindow.getDecorView();
+
+        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
+            mActionBar = new ActionBarImpl(this);
+        }
+
         WindowManager.LayoutParams l = mWindow.getAttributes();
         if ((l.softInputMode
                 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
@@ -834,12 +850,14 @@
     }
 
     public ActionMode onStartActionMode(ActionMode.Callback callback) {
-        // TODO Support context modes in dialogs
+        if (mActionBar != null) {
+            return mActionBar.startActionMode(callback);
+        }
         return null;
     }
 
     /**
-     * @return The activity associated with this dialog, or null if there is no assocaited activity.
+     * @return The activity associated with this dialog, or null if there is no associated activity.
      */
     private ComponentName getAssociatedActivity() {
         Activity activity = mOwnerActivity;
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index e7bdd8b..28abcaa 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -185,11 +185,14 @@
         void stop() {
             if (DEBUG) Log.v(TAG, "  Stopping: " + this);
             mStarted = false;
-            if (mLoader != null && mListenerRegistered) {
-                // Let the loader know we're done with it
-                mListenerRegistered = false;
-                mLoader.unregisterListener(this);
-                mLoader.stopLoading();
+            if (!mRetaining) {
+                if (mLoader != null && mListenerRegistered) {
+                    // Let the loader know we're done with it
+                    mListenerRegistered = false;
+                    mLoader.unregisterListener(this);
+                    mLoader.stopLoading();
+                }
+                mData = null;
             }
         }
         
diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java
index eaf0675..4dc88b3 100644
--- a/core/java/android/app/NativeActivity.java
+++ b/core/java/android/app/NativeActivity.java
@@ -10,6 +10,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.AssetManager;
+import android.content.res.Configuration;
 import android.graphics.PixelFormat;
 import android.os.Build;
 import android.os.Bundle;
@@ -32,12 +33,27 @@
 
 /**
  * Convenience for implementing an activity that will be implemented
- * purely in native code.  That is, a game (or game-like thing).
+ * purely in native code.  That is, a game (or game-like thing).  There
+ * is no need to derive from this class; you can simply declare it in your
+ * manifest, and use the NDK APIs from there.
+ *
+ * <p>A typical manifest would look like:
+ *
+ * {@sample development/ndk/platforms/android-9/samples/native-activity/AndroidManifest.xml
+ *      manifest}
+ *
+ * <p>A very simple example of native code that is run by NativeActivity
+ * follows.  This reads input events from the user and uses OpenGLES to
+ * draw into the native activity's window.
+ *
+ * {@sample development/ndk/platforms/android-9/samples/native-activity/jni/main.c all}
  */
 public class NativeActivity extends Activity implements SurfaceHolder.Callback2,
         InputQueue.Callback, OnGlobalLayoutListener {
     public static final String META_DATA_LIB_NAME = "android.app.lib_name";
     
+    public static final String KEY_NATIVE_SAVED_STATE = "android:native_state";
+
     private NativeContentView mNativeContentView;
     private InputMethodManager mIMM;
     private InputMethodCallback mInputMethodCallback;
@@ -59,14 +75,15 @@
     
     private native int loadNativeCode(String path, MessageQueue queue,
             String internalDataPath, String externalDataPath, int sdkVersion,
-            AssetManager assetMgr);
+            AssetManager assetMgr, byte[] savedState);
     private native void unloadNativeCode(int handle);
     
     private native void onStartNative(int handle);
     private native void onResumeNative(int handle);
-    private native void onSaveInstanceStateNative(int handle);
+    private native byte[] onSaveInstanceStateNative(int handle);
     private native void onPauseNative(int handle);
     private native void onStopNative(int handle);
+    private native void onConfigurationChangedNative(int handle);
     private native void onLowMemoryNative(int handle);
     private native void onWindowFocusChangedNative(int handle, boolean focused);
     private native void onSurfaceCreatedNative(int handle, Surface surface);
@@ -165,10 +182,13 @@
             throw new IllegalArgumentException("Unable to find native library: " + libname);
         }
         
+        byte[] nativeSavedState = savedInstanceState != null
+                ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null;
+
         mNativeHandle = loadNativeCode(path, Looper.myQueue(),
                  getFilesDir().toString(),
                  Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(),
-                 Build.VERSION.SDK_INT, getAssets());
+                 Build.VERSION.SDK_INT, getAssets(), nativeSavedState);
         
         if (mNativeHandle == 0) {
             throw new IllegalArgumentException("Unable to load native library: " + path);
@@ -206,7 +226,10 @@
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
-        onSaveInstanceStateNative(mNativeHandle);
+        byte[] state = onSaveInstanceStateNative(mNativeHandle);
+        if (state != null) {
+            outState.putByteArray(KEY_NATIVE_SAVED_STATE, state);
+        }
     }
 
     @Override
@@ -222,6 +245,14 @@
     }
 
     @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (!mDestroyed) {
+            onConfigurationChangedNative(mNativeHandle);
+        }
+    }
+
+    @Override
     public void onLowMemory() {
         super.onLowMemory();
         if (!mDestroyed) {
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 8eda844..03bcadc 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -468,12 +468,17 @@
      * <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however
      * many remote devices can only display the first 40 characters, and some
      * may be limited to just 20.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
      *
      * @param name a valid Bluetooth name
      * @return     true if the name was set, false otherwise
      */
     public boolean setName(String name) {
+        if (getState() != STATE_ON) return false;
         try {
             return mService.setName(name);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -488,11 +493,16 @@
      * {@link #SCAN_MODE_NONE},
      * {@link #SCAN_MODE_CONNECTABLE},
      * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
      *
      * @return scan mode
      */
     public int getScanMode() {
+        if (getState() != STATE_ON) return SCAN_MODE_NONE;
         try {
             return mService.getScanMode();
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -511,6 +521,10 @@
      * {@link #SCAN_MODE_NONE},
      * {@link #SCAN_MODE_CONNECTABLE},
      * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      * <p>Requires {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}
      * <p>Applications cannot set the scan mode. They should use
      * <code>startActivityForResult(
@@ -524,6 +538,7 @@
      * @hide
      */
     public boolean setScanMode(int mode, int duration) {
+        if (getState() != STATE_ON) return false;
         try {
             return mService.setScanMode(mode, duration);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -532,11 +547,13 @@
 
     /** @hide */
     public boolean setScanMode(int mode) {
+        if (getState() != STATE_ON) return false;
         return setScanMode(mode, 120);
     }
 
     /** @hide */
     public int getDiscoverableTimeout() {
+        if (getState() != STATE_ON) return -1;
         try {
             return mService.getDiscoverableTimeout();
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -545,6 +562,7 @@
 
     /** @hide */
     public void setDiscoverableTimeout(int timeout) {
+        if (getState() != STATE_ON) return;
         try {
             mService.setDiscoverableTimeout(timeout);
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -572,11 +590,16 @@
      * <p>Device discovery will only find remote devices that are currently
      * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are
      * not discoverable by default, and need to be entered into a special mode.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
      * @return true on success, false on error
      */
     public boolean startDiscovery() {
+        if (getState() != STATE_ON) return false;
         try {
             return mService.startDiscovery();
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -593,10 +616,15 @@
      * the  Activity, but is run as a system service, so an application should
      * always call cancel discovery even if it did not directly request a
      * discovery, just to be sure.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      *
      * @return true on success, false on error
      */
     public boolean cancelDiscovery() {
+        if (getState() != STATE_ON) return false;
         try {
             mService.cancelDiscovery();
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -614,11 +642,16 @@
      * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED}
      * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery
      * starts or completes.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
      *
      * @return true if discovering
      */
     public boolean isDiscovering() {
+        if (getState() != STATE_ON) return false;
         try {
             return mService.isDiscovering();
         } catch (RemoteException e) {Log.e(TAG, "", e);}
@@ -628,11 +661,18 @@
     /**
      * Return the set of {@link BluetoothDevice} objects that are bonded
      * (paired) to the local adapter.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return an empty set. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
      *
      * @return unmodifiable set of {@link BluetoothDevice}, or null on error
      */
     public Set<BluetoothDevice> getBondedDevices() {
+        if (getState() != STATE_ON) {
+            return toDeviceSet(new String[0]);
+        }
         try {
             return toDeviceSet(mService.listBonds());
         } catch (RemoteException e) {Log.e(TAG, "", e);}
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 1163add..e1d431f 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1001,7 +1001,7 @@
     /**
      * @hide -- until interface has proven itself
      *
-     * Call an provider-defined method.  This can be used to implement
+     * Call a provider-defined method.  This can be used to implement
      * interfaces that are cheaper than using a Cursor.
      *
      * @param method Method name to call.  Opaque to framework.
@@ -1013,29 +1013,26 @@
     }
 
     /**
-     * Shuts down this instance of the ContentProvider. It is useful when writing tests that use
-     * the ContentProvider.
+     * Implement this to shut down the ContentProvider instance. You can then
+     * invoke this method in unit tests.
+     * 
      * <p>
-     * If a unittest starts the ContentProvider in its test(..() methods, it could run into sqlite
-     * errors "disk I/O error" or "corruption" in the following scenario:
-     * <ul>
-     *   <li>Say, there are 2 test methods in the unittest</li>
-     *   <li>test1() (or setUp()) causes ContentProvider object to be initialized and
-     *   assume it opens a database connection to "foo.db"</li>
-     *   <li>est1() completes and test2() starts</li>
-     *   <li>During the execution of test2() there will be 2 connections to "foo.db"</li>
-     *   <li>Different threads in the ContentProvider may have one of these two connection
-     *   handles. This is not a problem per se</li>
-     *   <li>But if the two threads with 2 database connections don't interact correctly,
-     *   there could be unexpected errors from sqlite</li>
-     *   <li>Some of those unexpected errros are "disk I/O error" or "corruption" error</li>
-     *   <li>Common practice in tearDown() is to delete test directory (and the database files)</li>
-     *   <li>If this is done while some threads are still holding unclosed database connections,
-     *   sqlite quite easily gets into corruption and disk I/O errors</li>
-     * </ul>
+     * Android normally handles ContentProvider startup and shutdown
+     * automatically. You do not need to start up or shut down a
+     * ContentProvider. When you invoke a test method on a ContentProvider,
+     * however, a ContentProvider instance is started and keeps running after
+     * the test finishes, even if a succeeding test instantiates another
+     * ContentProvider. A conflict develops because the two instances are
+     * usually running against the same underlying data source (for example, an
+     * sqlite database).
+     * </p>
      * <p>
-     * tearDown() in the unittests should call this method to have ContentProvider gracefully
-     * shutdown all database connections.
+     * Implementing shutDown() avoids this conflict by providing a way to
+     * terminate the ContentProvider. This method can also prevent memory leaks
+     * from multiple instantiations of the ContentProvider, and it can ensure
+     * unit test isolation by allowing you to completely clean up the test
+     * fixture before moving on to the next test.
+     * </p>
      */
     public void shutdown() {
         Log.w(TAG, "implement ContentProvider shutdown() to make sure all database " +
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 850ff7f..42599ed 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -100,8 +100,8 @@
     public void stopLoading() {
         if (mCursor != null && !mCursor.isClosed()) {
             mCursor.close();
-            mCursor = null;
         }
+        mCursor = null;
 
         // Attempt to cancel the current load task if possible.
         cancelLoad();
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index ba1b3a9..812af5a 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1333,7 +1333,7 @@
                 height = mMetrics.widthPixels;
             }
             int keyboardHidden = mConfiguration.keyboardHidden;
-            if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO
+            if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
                     && mConfiguration.hardKeyboardHidden
                             == Configuration.HARDKEYBOARDHIDDEN_YES) {
                 keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 9ac45d8..c07c3c6 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -1203,4 +1203,18 @@
         }
         return STATEMENT_OTHER;
     }
+
+    /**
+     * Appends one set of selection args to another. This is useful when adding a selection
+     * argument to a user provided set.
+     */
+    public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
+        if (originalValues == null || originalValues.length == 0) {
+            return newValues;
+        }
+        String[] result = new String[originalValues.length + newValues.length ];
+        System.arraycopy(originalValues, 0, result, 0, originalValues.length);
+        System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
+        return result;
+    }
 }
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 11cd526..efbccd2 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -526,12 +526,6 @@
         return -1;
     }
 
-    /**
-     * This is not supported.
-     */
-    public void interpretScanResultsAvailable() {
-    }
-
     @Override
     public String toString() {
         StringBuffer sb = new StringBuffer("Mobile data state: ");
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 44215e7..82735e5 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -27,18 +27,17 @@
 public interface NetworkStateTracker {
 
     public static final int EVENT_STATE_CHANGED = 1;
-    public static final int EVENT_SCAN_RESULTS_AVAILABLE = 2;
     /**
      * arg1: 1 to show, 0 to hide
      * arg2: ID of the notification
      * obj: Notification (if showing)
      */
-    public static final int EVENT_NOTIFICATION_CHANGED = 3;
-    public static final int EVENT_CONFIGURATION_CHANGED = 4;
-    public static final int EVENT_ROAMING_CHANGED = 5;
-    public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6;
-    public static final int EVENT_RESTORE_DEFAULT_NETWORK = 7;
-    public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
+    public static final int EVENT_NOTIFICATION_CHANGED = 2;
+    public static final int EVENT_CONFIGURATION_CHANGED = 3;
+    public static final int EVENT_ROAMING_CHANGED = 4;
+    public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 5;
+    public static final int EVENT_RESTORE_DEFAULT_NETWORK = 6;
+    public static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 7;
 
     /**
      * Fetch NetworkInfo for the network
@@ -147,10 +146,4 @@
      */
     public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid);
 
-    /**
-     * Interprets scan results. This will be called at a safe time for
-     * processing, and from a safe thread.
-     */
-    public void interpretScanResultsAvailable();
-
 }
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 47faaba..63adcd0 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -1568,7 +1568,7 @@
             throw new UnsupportedOperationException(NOT_HIERARCHICAL);
         }
         if (key == null) {
-          throw new NullPointerException("key");
+            throw new NullPointerException("key");
         }
 
         final String query = getEncodedQuery();
@@ -1608,6 +1608,24 @@
         return null;
     }
 
+    /**
+     * Searches the query string for the first value with the given key and interprets it
+     * as a boolean value. "false" and "0" are interpreted as <code>false</code>, everything
+     * else is interpreted as <code>true</code>.
+     *
+     * @param key which will be decoded
+     * @param defaultValue the default value to return if there is no query parameter for key
+     * @return the boolean interpretation of the query parameter key
+     */
+    public boolean getBooleanQueryParameter(String key, boolean defaultValue) {
+        String flag = getQueryParameter(key);
+        if (flag == null) {
+            return defaultValue;
+        }
+        flag = flag.toLowerCase();
+        return (!"false".equals(flag) && !"0".equals(flag));
+    }
+
     /** Identifies a null parcelled Uri. */
     private static final int NULL_TYPE_ID = 0;
 
diff --git a/core/java/android/provider/Mtp.java b/core/java/android/provider/Mtp.java
index 15f8666..bc764ac 100644
--- a/core/java/android/provider/Mtp.java
+++ b/core/java/android/provider/Mtp.java
@@ -308,6 +308,13 @@
         public static final int FORMAT_ABSTRACT_CONTACT = 0xBB81;
         public static final int FORMAT_VCARD_2 = 0xBB82;
 
+        // Object properties we support
+        public static final int PROPERTY_STORAGE_ID = 0xDC01;
+        public static final int PROPERTY_OBJECT_FORMAT = 0xDC02;
+        public static final int PROPERTY_OBJECT_SIZE = 0xDC04;
+        public static final int PROPERTY_OBJECT_FILE_NAME = 0xDC07;
+        public static final int PROPERTY_PARENT_OBJECT = 0xDC0B;
+
         /**
          * Object is not protected. It may be modified and deleted, and its properties
          * may be modified.
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 96bd884..91dbe1f 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -527,11 +527,18 @@
     @Override
     public void drawPath(Path path, Paint paint) {
         boolean hasModifier = setupModifiers(paint);
-        nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+        if (path.isSimplePath) {
+            if (path.rects != null) {
+                nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
+            }
+        } else {
+            nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+        }
         if (hasModifier) nResetModifiers(mRenderer);
     }
 
     private native void nDrawPath(int renderer, int path, int paint);
+    private native void nDrawRects(int renderer, int region, int paint);
 
     @Override
     public void drawPicture(Picture picture) {
@@ -610,9 +617,13 @@
         if ((index | count | (index + count) | (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
+
         boolean hasModifier = setupModifiers(paint);
-        nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
-        if (hasModifier) nResetModifiers(mRenderer);
+        try {
+            nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
     }
     
     private native void nDrawText(int renderer, char[] text, int index, int count, float x, float y,
@@ -621,20 +632,23 @@
     @Override
     public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
         boolean hasModifier = setupModifiers(paint);
-        if (text instanceof String || text instanceof SpannedString ||
-                text instanceof SpannableString) {
-            nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
-                    paint.mNativePaint);
-        } else if (text instanceof GraphicsOperations) {
-            ((GraphicsOperations) text).drawText(this, start, end, x, y,
-                                                     paint);
-        } else {
-            char[] buf = TemporaryBuffer.obtain(end - start);
-            TextUtils.getChars(text, start, end, buf, 0);
-            nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint);
-            TemporaryBuffer.recycle(buf);
+        try {
+            if (text instanceof String || text instanceof SpannedString ||
+                    text instanceof SpannableString) {
+                nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
+                        paint.mNativePaint);
+            } else if (text instanceof GraphicsOperations) {
+                ((GraphicsOperations) text).drawText(this, start, end, x, y,
+                                                         paint);
+            } else {
+                char[] buf = TemporaryBuffer.obtain(end - start);
+                TextUtils.getChars(text, start, end, buf, 0);
+                nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint);
+                TemporaryBuffer.recycle(buf);
+            }
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
         }
-        if (hasModifier) nResetModifiers(mRenderer);
     }
 
     @Override
@@ -642,9 +656,13 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
+
         boolean hasModifier = setupModifiers(paint);
-        nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
-        if (hasModifier) nResetModifiers(mRenderer);
+        try {
+            nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
     }
 
     private native void nDrawText(int renderer, String text, int start, int end, float x, float y,
@@ -653,8 +671,12 @@
     @Override
     public void drawText(String text, float x, float y, Paint paint) {
         boolean hasModifier = setupModifiers(paint);
-        nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, paint.mNativePaint);
-        if (hasModifier) nResetModifiers(mRenderer);
+        try {
+            nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags,
+                    paint.mNativePaint);
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
     }
 
     @Override
@@ -671,15 +693,59 @@
     @Override
     public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
             float x, float y, int dir, Paint paint) {
-        throw new UnsupportedOperationException();
+        if ((index | count | text.length - index - count) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (dir != DIRECTION_LTR && dir != DIRECTION_RTL) {
+            throw new IllegalArgumentException("Unknown direction: " + dir);
+        }
+
+        boolean hasModifier = setupModifiers(paint);
+        try {
+            nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, dir,
+                    paint.mNativePaint);
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
     }
 
+    private native void nDrawTextRun(int renderer, char[] text, int index, int count,
+            int contextIndex, int contextCount, float x, float y, int dir, int nativePaint);
+
     @Override
     public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
             float x, float y, int dir, Paint paint) {
-        throw new UnsupportedOperationException();
+        if ((start | end | end - start | text.length() - end) < 0) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        boolean hasModifier = setupModifiers(paint);
+        try {
+            int flags = dir == 0 ? 0 : 1;
+            if (text instanceof String || text instanceof SpannedString ||
+                    text instanceof SpannableString) {
+                nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
+                        contextEnd, x, y, flags, paint.mNativePaint);
+            } else if (text instanceof GraphicsOperations) {
+                ((GraphicsOperations) text).drawTextRun(this, start, end,
+                        contextStart, contextEnd, x, y, flags, paint);
+            } else {
+                int contextLen = contextEnd - contextStart;
+                int len = end - start;
+                char[] buf = TemporaryBuffer.obtain(contextLen);
+                TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+                nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
+                        x, y, flags, paint.mNativePaint);
+                TemporaryBuffer.recycle(buf);
+            }
+        } finally {
+            if (hasModifier) nResetModifiers(mRenderer);
+        }
     }
 
+    private native void nDrawTextRun(int renderer, String text, int start, int end,
+            int contextStart, int contextEnd, float x, float y, int flags, int nativePaint);
+
     @Override
     public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
             float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 60d495f..cd6b820 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -34,12 +34,23 @@
  * 
  * @hide
  */
-abstract class HardwareRenderer {
+public abstract class HardwareRenderer {
     private boolean mEnabled;
     private boolean mRequested = true;
     private static final String LOG_TAG = "HardwareRenderer";
 
     /**
+     * Indicates whether hardware acceleration is available under any form for
+     * the view hierarchy.
+     * 
+     * @return True if the view hierarchy can potentially be hardware accelerated,
+     *         false otherwise
+     */
+    public static boolean isAvailable() {
+        return GLES20Canvas.isAvailable();
+    }
+
+    /**
      * Destroys the hardware rendering context.
      */
     abstract void destroy();
diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java
index 13d8104..43c957a 100644
--- a/core/java/android/view/InputQueue.java
+++ b/core/java/android/view/InputQueue.java
@@ -132,9 +132,9 @@
             synchronized (sLock) {
                 FinishedCallback callback = sRecycleHead;
                 if (callback != null) {
-                    callback.mRecycleNext = null;
                     sRecycleHead = callback.mRecycleNext;
                     sRecycleCount -= 1;
+                    callback.mRecycleNext = null;
                 } else {
                     callback = new FinishedCallback();
                 }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index 9223e17..ed10e41 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -156,7 +156,7 @@
     //  those new codes.  This is intended to maintain a consistent
     //  set of key code definitions across all Android devices.
    
-    private static final int LAST_KEYCODE           = KEYCODE_SWITCH_CHARSET;
+    private static final int LAST_KEYCODE           = KEYCODE_BUTTON_MODE;
     
     /**
      * @deprecated There are now more than MAX_KEYCODE keycodes.
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index ff34f4a..0999598 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -312,7 +312,7 @@
      * MotionEvent has no getRawX(int) method; simulate it pending future API approval. 
      */
     private static float getRawX(MotionEvent event, int pointerIndex) {
-        float offset = event.getX() - event.getRawX();
+        float offset = event.getRawX() - event.getX();
         return event.getX(pointerIndex) + offset;
     }
     
@@ -320,7 +320,7 @@
      * MotionEvent has no getRawY(int) method; simulate it pending future API approval. 
      */
     private static float getRawY(MotionEvent event, int pointerIndex) {
-        float offset = event.getY() - event.getRawY();
+        float offset = event.getRawY() - event.getY();
         return event.getY(pointerIndex) + offset;
     }
 
diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java
index 068e7b6..fb88c71 100644
--- a/core/java/android/view/VelocityTracker.java
+++ b/core/java/android/view/VelocityTracker.java
@@ -33,14 +33,15 @@
  * and {@link #getXVelocity()}.
  */
 public final class VelocityTracker implements Poolable<VelocityTracker> {
-    static final String TAG = "VelocityTracker";
-    static final boolean DEBUG = false;
-    static final boolean localLOGV = DEBUG || Config.LOGV;
+    private static final String TAG = "VelocityTracker";
+    private static final boolean DEBUG = false;
+    private static final boolean localLOGV = DEBUG || Config.LOGV;
 
-    static final int NUM_PAST = 10;
-    static final int MAX_AGE_MILLISECONDS = 200;
+    private static final int NUM_PAST = 10;
+    private static final int MAX_AGE_MILLISECONDS = 200;
+    
+    private static final int POINTER_POOL_CAPACITY = 20;
 
-    static final VelocityTracker[] mPool = new VelocityTracker[1];
     private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
             Pools.finitePool(new PoolableManager<VelocityTracker>() {
                 public VelocityTracker newInstance() {
@@ -48,16 +49,19 @@
                 }
 
                 public void onAcquired(VelocityTracker element) {
-                    element.clear();
                 }
 
                 public void onReleased(VelocityTracker element) {
+                    element.clear();
                 }
             }, 2));
     
-    private static final int INITIAL_POINTERS = 5;
+    private static Pointer sRecycledPointerListHead;
+    private static int sRecycledPointerCount;
     
-    private static final class PointerData {
+    private static final class Pointer {
+        public Pointer next;
+        
         public int id;
         public float xVelocity;
         public float yVelocity;
@@ -65,11 +69,13 @@
         public final float[] pastX = new float[NUM_PAST];
         public final float[] pastY = new float[NUM_PAST];
         public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
+        
+        public int generation;
     }
     
-    private PointerData[] mPointers = new PointerData[INITIAL_POINTERS];
-    private int mNumPointers;
+    private Pointer mPointerListHead; // sorted by id in increasing order
     private int mLastTouchIndex;
+    private int mGeneration;
 
     private VelocityTracker mNext;
 
@@ -115,7 +121,9 @@
      * Reset the velocity tracker back to its initial state.
      */
     public void clear() {
-        mNumPointers = 0;
+        releasePointerList(mPointerListHead);
+        
+        mPointerListHead = null;
         mLastTouchIndex = 0;
     }
     
@@ -134,56 +142,62 @@
         final int lastTouchIndex = mLastTouchIndex;
         final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
         final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
+        final int generation = mGeneration++;
         
-        if (pointerCount < mNumPointers) {
-            final PointerData[] pointers = mPointers;
-            int i = mNumPointers;
-            while (--i >= 0) {
-                final PointerData pointerData = pointers[i];
-                if (ev.findPointerIndex(pointerData.id) == -1) {
-                    // Pointer went up.
-                    // Shuffle pointers down to fill the hole.  Place the old pointer data at
-                    // the end so we can recycle it if more pointers are added later.
-                    mNumPointers -= 1;
-                    final int remaining = mNumPointers - i;
-                    if (remaining != 0) {
-                        System.arraycopy(pointers, i + 1, pointers, i, remaining);
-                        pointers[mNumPointers] = pointerData;
-                    }
-                }
-            }
-        }
-        
+        mLastTouchIndex = finalTouchIndex;
+
+        // Update pointer data.
+        Pointer previousPointer = null;
         for (int i = 0; i < pointerCount; i++){
             final int pointerId = ev.getPointerId(i);
-            PointerData pointerData = getPointerData(pointerId);
-            if (pointerData == null) {
-                // Pointer went down.
-                // Add a new entry.  Write a sentinel at the end of the pastTime trace so we
-                // will be able to tell where the trace started.
-                final PointerData[] oldPointers = mPointers;
-                final int newPointerIndex = mNumPointers;
-                if (newPointerIndex < oldPointers.length) {
-                    pointerData = oldPointers[newPointerIndex];
-                    if (pointerData == null) {
-                        pointerData = new PointerData();
-                        oldPointers[newPointerIndex] = pointerData;
-                    }
-                } else {
-                    final PointerData[] newPointers = new PointerData[newPointerIndex * 2];
-                    System.arraycopy(oldPointers, 0, newPointers, 0, newPointerIndex);
-                    mPointers = newPointers;
-                    pointerData = new PointerData();
-                    newPointers[newPointerIndex] = pointerData;
-                }
-                pointerData.id = pointerId;
-                pointerData.pastTime[lastTouchIndex] = Long.MIN_VALUE;
-                mNumPointers += 1;
+            
+            // Find the pointer data for this pointer id.
+            // This loop is optimized for the common case where pointer ids in the event
+            // are in sorted order.  However, we check for this case explicitly and
+            // perform a full linear scan from the start if needed.
+            Pointer nextPointer;
+            if (previousPointer == null || pointerId < previousPointer.id) {
+                previousPointer = null;
+                nextPointer = mPointerListHead;
+            } else {
+                nextPointer = previousPointer.next;
             }
             
-            final float[] pastX = pointerData.pastX;
-            final float[] pastY = pointerData.pastY;
-            final long[] pastTime = pointerData.pastTime;
+            final Pointer pointer;
+            for (;;) {
+                if (nextPointer != null) {
+                    final int nextPointerId = nextPointer.id;
+                    if (nextPointerId == pointerId) {
+                        pointer = nextPointer;
+                        break;
+                    }
+                    if (nextPointerId < pointerId) {
+                        nextPointer = nextPointer.next;
+                        continue;
+                    }
+                }
+                
+                // Pointer went down.  Add it to the list.
+                // Write a sentinel at the end of the pastTime trace so we will be able to
+                // tell when the trace started.
+                pointer = obtainPointer();
+                pointer.id = pointerId;
+                pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
+                pointer.next = nextPointer;
+                if (previousPointer == null) {
+                    mPointerListHead = pointer;
+                } else {
+                    previousPointer.next = pointer;
+                }
+                break;
+            }
+            
+            pointer.generation = generation;
+            previousPointer = pointer;
+            
+            final float[] pastX = pointer.pastX;
+            final float[] pastY = pointer.pastY;
+            final long[] pastTime = pointer.pastTime;
             
             for (int j = 0; j < historySize; j++) {
                 final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
@@ -196,7 +210,23 @@
             pastTime[finalTouchIndex] = ev.getEventTime();
         }
         
-        mLastTouchIndex = finalTouchIndex;
+        // Find removed pointers.
+        previousPointer = null;
+        for (Pointer pointer = mPointerListHead; pointer != null; ) {
+            final Pointer nextPointer = pointer.next;
+            if (pointer.generation != generation) {
+                // Pointer went up.  Remove it from the list.
+                if (previousPointer == null) {
+                    mPointerListHead = nextPointer;
+                } else {
+                    previousPointer.next = nextPointer;
+                }
+                releasePointer(pointer);
+            } else {
+                previousPointer = pointer;
+            }
+            pointer = nextPointer;
+        }
     }
 
     /**
@@ -223,13 +253,10 @@
      * must be positive.
      */
     public void computeCurrentVelocity(int units, float maxVelocity) {
-        final int numPointers = mNumPointers;
-        final PointerData[] pointers = mPointers;
         final int lastTouchIndex = mLastTouchIndex;
         
-        for (int p = 0; p < numPointers; p++) {
-            final PointerData pointerData = pointers[p];
-            final long[] pastTime = pointerData.pastTime;
+        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
+            final long[] pastTime = pointer.pastTime;
             
             // Search backwards in time for oldest acceptable time.
             // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
@@ -253,8 +280,8 @@
             }
             
             // Kind-of stupid.
-            final float[] pastX = pointerData.pastX;
-            final float[] pastY = pointerData.pastY;
+            final float[] pastX = pointer.pastX;
+            final float[] pastY = pointer.pastY;
             
             final float oldestX = pastX[oldestTouchIndex];
             final float oldestY = pastY[oldestTouchIndex];
@@ -290,11 +317,11 @@
                 accumY = maxVelocity;
             }
             
-            pointerData.xVelocity = accumX;
-            pointerData.yVelocity = accumY;
+            pointer.xVelocity = accumX;
+            pointer.yVelocity = accumY;
             
             if (localLOGV) {
-                Log.v(TAG, "[" + p + "] Pointer " + pointerData.id
+                Log.v(TAG, "Pointer " + pointer.id
                     + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
             }
         }
@@ -307,8 +334,8 @@
      * @return The previously computed X velocity.
      */
     public float getXVelocity() {
-        PointerData pointerData = getPointerData(0);
-        return pointerData != null ? pointerData.xVelocity : 0;
+        Pointer pointer = getPointer(0);
+        return pointer != null ? pointer.xVelocity : 0;
     }
     
     /**
@@ -318,8 +345,8 @@
      * @return The previously computed Y velocity.
      */
     public float getYVelocity() {
-        PointerData pointerData = getPointerData(0);
-        return pointerData != null ? pointerData.yVelocity : 0;
+        Pointer pointer = getPointer(0);
+        return pointer != null ? pointer.yVelocity : 0;
     }
     
     /**
@@ -330,8 +357,8 @@
      * @return The previously computed X velocity.
      */
     public float getXVelocity(int id) {
-        PointerData pointerData = getPointerData(id);
-        return pointerData != null ? pointerData.xVelocity : 0;
+        Pointer pointer = getPointer(id);
+        return pointer != null ? pointer.xVelocity : 0;
     }
     
     /**
@@ -342,19 +369,68 @@
      * @return The previously computed Y velocity.
      */
     public float getYVelocity(int id) {
-        PointerData pointerData = getPointerData(id);
-        return pointerData != null ? pointerData.yVelocity : 0;
+        Pointer pointer = getPointer(id);
+        return pointer != null ? pointer.yVelocity : 0;
     }
     
-    private final PointerData getPointerData(int id) {
-        final PointerData[] pointers = mPointers;
-        final int numPointers = mNumPointers;
-        for (int p = 0; p < numPointers; p++) {
-            PointerData pointerData = pointers[p];
-            if (pointerData.id == id) {
-                return pointerData;
+    private final Pointer getPointer(int id) {
+        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
+            if (pointer.id == id) {
+                return pointer;
             }
         }
         return null;
     }
+    
+    private static final Pointer obtainPointer() {
+        synchronized (sPool) {
+            if (sRecycledPointerCount != 0) {
+                Pointer element = sRecycledPointerListHead;
+                sRecycledPointerCount -= 1;
+                sRecycledPointerListHead = element.next;
+                element.next = null;
+                return element;
+            }
+        }
+        return new Pointer();
+    }
+    
+    private static final void releasePointer(Pointer pointer) {
+        synchronized (sPool) {
+            if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
+                pointer.next = sRecycledPointerListHead;
+                sRecycledPointerCount += 1;
+                sRecycledPointerListHead = pointer;
+            }
+        }
+    }
+    
+    private static final void releasePointerList(Pointer pointer) {
+        if (pointer != null) {
+            synchronized (sPool) {
+                int count = sRecycledPointerCount;
+                if (count >= POINTER_POOL_CAPACITY) {
+                    return;
+                }
+                
+                Pointer tail = pointer;
+                for (;;) {
+                    count += 1;
+                    if (count >= POINTER_POOL_CAPACITY) {
+                        break;
+                    }
+                    
+                    Pointer next = tail.next;
+                    if (next == null) {
+                        break;
+                    }
+                    tail = next;
+                }
+
+                tail.next = sRecycledPointerListHead;
+                sRecycledPointerCount = count;
+                sRecycledPointerListHead = pointer;
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 735b35a..570793b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.graphics.Camera;
 import com.android.internal.R;
 import com.android.internal.view.menu.MenuBuilder;
 
@@ -1374,14 +1375,14 @@
      * Width as measured during measure pass.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     protected int mMeasuredWidth;
 
     /**
      * Height as measured during measure pass.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     protected int mMeasuredHeight;
 
     /**
@@ -1436,8 +1437,8 @@
     static final int MEASURED_DIMENSION_SET         = 0x00000800;
     /** {@hide} */
     static final int FORCE_LAYOUT                   = 0x00001000;
-
-    private static final int LAYOUT_REQUIRED        = 0x00002000;
+    /** {@hide} */
+    static final int LAYOUT_REQUIRED                = 0x00002000;
 
     private static final int PRESSED                = 0x00004000;
 
@@ -1537,6 +1538,14 @@
     private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000;
 
     /**
+     * Indicates that pivotX or pivotY were explicitly set and we should not assume the center
+     * for transform operations
+     *
+     * @hide
+     */
+    private static final int PIVOT_EXPLICITLY_SET = 0x10000000;
+
+    /**
      * The parent this view is attached to.
      * {@hide}
      *
@@ -1627,6 +1636,42 @@
     private boolean mMatrixIsIdentity = true;
 
     /**
+     * The Camera object is used to compute a 3D matrix when rotationX or rotationY are set.
+     */
+    private Camera mCamera = null;
+
+    /**
+     * This matrix is used when computing the matrix for 3D rotations.
+     */
+    private Matrix matrix3D = null;
+
+    /**
+     * These prev values are used to recalculate a centered pivot point when necessary. The
+     * pivot point is only used in matrix operations (when rotation, scale, or translation are
+     * set), so thes values are only used then as well.
+     */
+    private int mPrevWidth = -1;
+    private int mPrevHeight = -1;
+
+    /**
+     * Convenience value to check for float values that are close enough to zero to be considered
+     * zero.
+     */
+    private static float NONZERO_EPSILON = .001f;
+
+    /**
+     * The degrees rotation around the vertical axis through the pivot point.
+     */
+    @ViewDebug.ExportedProperty
+    private float mRotationY = 0f;
+
+    /**
+     * The degrees rotation around the horizontal axis through the pivot point.
+     */
+    @ViewDebug.ExportedProperty
+    private float mRotationX = 0f;
+
+    /**
      * The degrees rotation around the pivot point.
      */
     @ViewDebug.ExportedProperty
@@ -1684,28 +1729,28 @@
      * to the left edge of this view.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     protected int mLeft;
     /**
      * The distance in pixels from the left edge of this view's parent
      * to the right edge of this view.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     protected int mRight;
     /**
      * The distance in pixels from the top edge of this view's parent
      * to the top edge of this view.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     protected int mTop;
     /**
      * The distance in pixels from the top edge of this view's parent
      * to the bottom edge of this view.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     protected int mBottom;
 
     /**
@@ -1713,14 +1758,14 @@
      * horizontally.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "scrolling")
     protected int mScrollX;
     /**
      * The offset, in pixels, by which the content of this view is scrolled
      * vertically.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "scrolling")
     protected int mScrollY;
 
     /**
@@ -1728,28 +1773,28 @@
      * left edge of this view and the left edge of its content.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "padding")
     protected int mPaddingLeft;
     /**
      * The right padding in pixels, that is the distance in pixels between the
      * right edge of this view and the right edge of its content.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "padding")
     protected int mPaddingRight;
     /**
      * The top padding in pixels, that is the distance in pixels between the
      * top edge of this view and the top edge of its content.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "padding")
     protected int mPaddingTop;
     /**
      * The bottom padding in pixels, that is the distance in pixels between the
      * bottom edge of this view and the bottom edge of its content.
      * {@hide}
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "padding")
     protected int mPaddingBottom;
 
     /**
@@ -1760,13 +1805,13 @@
     /**
      * Cache the paddingRight set by the user to append to the scrollbar's size.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "padding")
     int mUserPaddingRight;
 
     /**
      * Cache the paddingBottom set by the user to append to the scrollbar's size.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "padding")
     int mUserPaddingBottom;
 
     /**
@@ -1873,14 +1918,14 @@
      * The minimum height of the view. We'll try our best to have the height
      * of this view to at least this amount.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     private int mMinHeight;
 
     /**
      * The minimum width of the view. We'll try our best to have the width
      * of this view to at least this amount.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     private int mMinWidth;
 
     /**
@@ -2722,7 +2767,7 @@
      *
      * @return True if this view has or contains focus, false otherwise.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "focus")
     public boolean hasFocus() {
         return (mPrivateFlags & FOCUSED) != 0;
     }
@@ -2900,7 +2945,7 @@
      *
      * @return True if this view has focus, false otherwise.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "focus")
     public boolean isFocused() {
         return (mPrivateFlags & FOCUSED) != 0;
     }
@@ -3311,7 +3356,7 @@
      *
      * @return true if this view has nothing to draw, false otherwise
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean willNotDraw() {
         return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW;
     }
@@ -3334,7 +3379,7 @@
      *
      * @return true if this view does not cache its drawing, false otherwise
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean willNotCacheDrawing() {
         return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING;
     }
@@ -3509,7 +3554,7 @@
      * @return True if this view can take focus, or false otherwise.
      * @attr ref android.R.styleable#View_focusable
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "focus")
     public final boolean isFocusable() {
         return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK);
     }
@@ -4820,7 +4865,7 @@
      *
      * @return The width of your view, in pixels.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     public final int getWidth() {
         return mRight - mLeft;
     }
@@ -4830,7 +4875,7 @@
      *
      * @return The height of your view, in pixels.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     public final int getHeight() {
         return mBottom - mTop;
     }
@@ -4888,6 +4933,16 @@
     }
 
     /**
+     * Utility function to determine if the value is far enough away from zero to be
+     * considered non-zero.
+     * @param value A floating point value to check for zero-ness
+     * @return whether the passed-in value is far enough away from zero to be considered non-zero
+     */
+    private static boolean nonzero(float value) {
+        return (value < -NONZERO_EPSILON || value > NONZERO_EPSILON);
+    }
+
+    /**
      * Recomputes the transform matrix if necessary.
      * 
      * @return True if the transform matrix is the identity matrix, false otherwise.
@@ -4896,10 +4951,34 @@
         if (mMatrixDirty) {
             // transform-related properties have changed since the last time someone
             // asked for the matrix; recalculate it with the current values
+
+            // Figure out if we need to update the pivot point
+            if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) {
+                if ((mRight - mLeft) != mPrevWidth && (mBottom - mTop) != mPrevHeight) {
+                    mPrevWidth = mRight - mLeft;
+                    mPrevHeight = mBottom - mTop;
+                    mPivotX = (float) mPrevWidth / 2f;
+                    mPivotY = (float) mPrevHeight / 2f;
+                }
+            }
             mMatrix.reset();
             mMatrix.setTranslate(mTranslationX, mTranslationY);
             mMatrix.preRotate(mRotation, mPivotX, mPivotY);
             mMatrix.preScale(mScaleX, mScaleY, mPivotX, mPivotY);
+            if (nonzero(mRotationX) || nonzero(mRotationY)) {
+                if (mCamera == null) {
+                    mCamera = new Camera();
+                    matrix3D = new Matrix();
+                }
+                mCamera.save();
+                mCamera.rotateX(mRotationX);
+                mCamera.rotateY(mRotationY);
+                mCamera.getMatrix(matrix3D);
+                matrix3D.preTranslate(-mPivotX, -mPivotY);
+                matrix3D.postTranslate(mPivotX, mPivotY);
+                mMatrix.postConcat(matrix3D);
+                mCamera.restore();
+            }
             mMatrixDirty = false;
             mMatrixIsIdentity = mMatrix.isIdentity();
             mInverseMatrixDirty = true;
@@ -4955,6 +5034,64 @@
     }
 
     /**
+     * The degrees that the view is rotated around the vertical axis through the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The degrees of Y rotation.
+     */
+    public float getRotationY() {
+        return mRotationY;
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the vertical axis through pivot point.
+     *
+     * @param rotationY The degrees of Y rotation.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setRotationY(float rotationY) {
+        if (mRotationY != rotationY) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mRotationY = rotationY;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
+     * The degrees that the view is rotated around the horizontal axis through the pivot point.
+     *
+     * @see #getPivotX()
+     * @see #getPivotY()
+     * @return The degrees of X rotation.
+     */
+    public float getRotationX() {
+        return mRotationX;
+    }
+
+    /**
+     * Sets the degrees that the view is rotated around the horizontal axis through pivot point.
+     *
+     * @param rotationX The degrees of X rotation.
+     * @see #getPivotX()
+     * @see #getPivotY()
+     */
+    public void setRotationX(float rotationX) {
+        if (mRotationX != rotationX) {
+            // Double-invalidation is necessary to capture view's old and new areas
+            invalidate();
+            mRotationX = rotationX;
+            mMatrixDirty = true;
+            mPrivateFlags |= DRAWN; // force another invalidation with the new orientation
+            invalidate();
+        }
+    }
+
+    /**
      * The amount that the view is scaled in x around the pivot point, as a proportion of
      * the view's unscaled width. A value of 1, the default, means that no scaling is applied.
      *
@@ -5035,6 +5172,9 @@
     /**
      * Sets the x location of the point around which the view is
      * {@link #setRotation(float) rotated} and {@link #setScaleX(float) scaled}.
+     * By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
      *
      * @param pivotX The x location of the pivot point.
      * @see #getRotation()
@@ -5043,6 +5183,7 @@
      * @see #getPivotY()
      */
     public void setPivotX(float pivotX) {
+        mPrivateFlags |= PIVOT_EXPLICITLY_SET;
         if (mPivotX != pivotX) {
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate();
@@ -5069,7 +5210,9 @@
 
     /**
      * Sets the y location of the point around which the view is {@link #setRotation(float) rotated}
-     * and {@link #setScaleY(float) scaled}.
+     * and {@link #setScaleY(float) scaled}. By default, the pivot point is centered on the object.
+     * Setting this property disables this behavior and causes the view to use only the
+     * explicitly set pivotX and pivotY values.
      *
      * @param pivotY The y location of the pivot point.
      * @see #getRotation()
@@ -5078,6 +5221,7 @@
      * @see #getPivotY()
      */
     public void setPivotY(float pivotY) {
+        mPrivateFlags |= PIVOT_EXPLICITLY_SET;
         if (mPivotY != pivotY) {
             // Double-invalidation is necessary to capture view's old and new areas
             invalidate();
@@ -5312,15 +5456,6 @@
      * is still within the view.
      */
     private boolean pointInView(float localX, float localY, float slop) {
-        if (!hasIdentityMatrix() && mAttachInfo != null) {
-            // non-identity matrix: transform the point into the view's coordinates
-            final float[] localXY = mAttachInfo.mTmpTransformLocation;
-            localXY[0] = localX;
-            localXY[1] = localY;
-            getInverseMatrix().mapPoints(localXY);
-            localX = localXY[0];
-            localY = localXY[1];
-        }
         return localX > -slop && localY > -slop && localX < ((mRight - mLeft) + slop) &&
                 localY < ((mBottom - mTop) + slop);
     }
@@ -5781,7 +5916,7 @@
      *
      * @return True if this View is guaranteed to be fully opaque, false otherwise.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isOpaque() {
         return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK;
     }
@@ -6866,7 +7001,7 @@
      * @see #setDrawingCacheEnabled(boolean)
      * @see #getDrawingCache()
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isDrawingCacheEnabled() {
         return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED;
     }
@@ -8730,7 +8865,7 @@
      * @return the offset of the baseline within the widget's bounds or -1
      *         if baseline alignment is not supported
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     public int getBaseline() {
         return -1;
     }
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 5dd45f9..2ca08ea 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -255,6 +255,14 @@
          * @see #deepExport()
          */
         String prefix() default "";
+
+        /**
+         * Specifies the category the property falls into, such as measurement,
+         * layout, drawing, etc.
+         *
+         * @return the category as String
+         */
+        String category() default "";
     }
 
     /**
@@ -934,65 +942,76 @@
 
     private static void profileViewAndChildren(final View view, BufferedWriter out)
             throws IOException {
-        final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
-            public Void[] pre() {
-                forceLayout(view);
-                return null;
-            }
+        profileViewAndChildren(view, out, true);
+    }
 
-            private void forceLayout(View view) {
-                view.forceLayout();
-                if (view instanceof ViewGroup) {
-                    ViewGroup group = (ViewGroup) view;
-                    final int count = group.getChildCount();
-                    for (int i = 0; i < count; i++) {
-                        forceLayout(group.getChildAt(i));
-                    }
-                }
-            }
+    private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root)
+            throws IOException {
 
-            public void run(Void... data) {
-                view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
-            }
+        long durationMeasure =
+                (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation(
+                        view, new ViewOperation<Void>() {
+                            public Void[] pre() {
+                                forceLayout(view);
+                                return null;
+                            }
 
-            public void post(Void... data) {
-            }
-        });
+                            private void forceLayout(View view) {
+                                view.forceLayout();
+                                if (view instanceof ViewGroup) {
+                                    ViewGroup group = (ViewGroup) view;
+                                    final int count = group.getChildCount();
+                                    for (int i = 0; i < count; i++) {
+                                        forceLayout(group.getChildAt(i));
+                                    }
+                                }
+                            }
 
-        final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
-            public Void[] pre() {
-                return null;
-            }
+                            public void run(Void... data) {
+                                view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+                            }
 
-            public void run(Void... data) {
-                view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
-            }
+                            public void post(Void... data) {
+                            }
+                        })
+                        : 0;
+        long durationLayout =
+                (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation(
+                        view, new ViewOperation<Void>() {
+                            public Void[] pre() {
+                                return null;
+                            }
 
-            public void post(Void... data) {
-            }
-        });
+                            public void run(Void... data) {
+                                view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
+                            }
 
-        final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
-            public Object[] pre() {
-                final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
-                final Bitmap bitmap =
-                        Bitmap.createBitmap(metrics.widthPixels, metrics.heightPixels,
-                                Bitmap.Config.RGB_565);
-                final Canvas canvas = new Canvas(bitmap);
-                return new Object[] {
-                        bitmap, canvas
-                };
-            }
+                            public void post(Void... data) {
+                            }
+                        }) : 0;
+        long durationDraw =
+                (root || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation(view,
+                        new ViewOperation<Object>() {
+                            public Object[] pre() {
+                                final DisplayMetrics metrics =
+                                        view.getResources().getDisplayMetrics();
+                                final Bitmap bitmap =
+                                        Bitmap.createBitmap(metrics.widthPixels,
+                                                metrics.heightPixels, Bitmap.Config.RGB_565);
+                                final Canvas canvas = new Canvas(bitmap);
+                                return new Object[] {
+                                        bitmap, canvas
+                                };
+                            }
 
-            public void run(Object... data) {
-                view.draw((Canvas) data[1]);
-            }
+                            public void run(Object... data) {
+                                view.draw((Canvas) data[1]);
+                            }
 
-            public void post(Object... data) {
-                ((Bitmap) data[0]).recycle();
-            }
-        });
-
+                            public void post(Object... data) {
+                                ((Bitmap) data[0]).recycle();
+                            }
+                        }) : 0;
         out.write(String.valueOf(durationMeasure));
         out.write(' ');
         out.write(String.valueOf(durationLayout));
@@ -1003,7 +1022,7 @@
             ViewGroup group = (ViewGroup) view;
             final int count = group.getChildCount();
             for (int i = 0; i < count; i++) {
-                profileViewAndChildren(group.getChildAt(i), out);
+                profileViewAndChildren(group.getChildAt(i), out, false);
             }
         }
     }
@@ -1033,7 +1052,10 @@
         });
 
         try {
-            latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS);
+            if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) {
+                Log.w("View", "Could not complete the profiling of the view " + view);
+                return -1;
+            }
         } catch (InterruptedException e) {
             Log.w("View", "Could not complete the profiling of the view " + view);
             Thread.currentThread().interrupt();
@@ -1354,9 +1376,12 @@
                 // TODO: This should happen on the UI thread
                 Object methodValue = method.invoke(view, (Object[]) null);
                 final Class<?> returnType = method.getReturnType();
+                final ExportedProperty property = sAnnotations.get(method);
+                String categoryPrefix =
+                        property.category().length() != 0 ? property.category() + ":" : "";
 
                 if (returnType == int.class) {
-                    final ExportedProperty property = sAnnotations.get(method);
+
                     if (property.resolveId() && context != null) {
                         final int id = (Integer) methodValue;
                         methodValue = resolveId(context, id);
@@ -1364,7 +1389,8 @@
                         final FlagToString[] flagsMapping = property.flagMapping();
                         if (flagsMapping.length > 0) {
                             final int intValue = (Integer) methodValue;
-                            final String valuePrefix = prefix + method.getName() + '_';
+                            final String valuePrefix =
+                                    categoryPrefix + prefix + method.getName() + '_';
                             exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
                         }
 
@@ -1388,21 +1414,22 @@
                         }
                     }
                 } else if (returnType == int[].class) {
-                    final ExportedProperty property = sAnnotations.get(method);
                     final int[] array = (int[]) methodValue;
-                    final String valuePrefix = prefix + method.getName() + '_';
+                    final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
                     final String suffix = "()";
 
                     exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
+
+                    // Probably want to return here, same as for fields.
+                    return;
                 } else if (!returnType.isPrimitive()) {
-                    final ExportedProperty property = sAnnotations.get(method);
                     if (property.deepExport()) {
                         dumpViewProperties(context, methodValue, out, prefix + property.prefix());
                         continue;
                     }
                 }
 
-                writeEntry(out, prefix, method.getName(), "()", methodValue);
+                writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
             } catch (IllegalAccessException e) {
             } catch (InvocationTargetException e) {
             }
@@ -1422,9 +1449,12 @@
             try {
                 Object fieldValue = null;
                 final Class<?> type = field.getType();
+                final ExportedProperty property = sAnnotations.get(field);
+                String categoryPrefix =
+                        property.category().length() != 0 ? property.category() + ":" : "";
 
                 if (type == int.class) {
-                    final ExportedProperty property = sAnnotations.get(field);
+
                     if (property.resolveId() && context != null) {
                         final int id = field.getInt(view);
                         fieldValue = resolveId(context, id);
@@ -1432,7 +1462,8 @@
                         final FlagToString[] flagsMapping = property.flagMapping();
                         if (flagsMapping.length > 0) {
                             final int intValue = field.getInt(view);
-                            final String valuePrefix = prefix + field.getName() + '_';
+                            final String valuePrefix =
+                                    categoryPrefix + prefix + field.getName() + '_';
                             exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
                         }
 
@@ -1454,9 +1485,8 @@
                         }
                     }
                 } else if (type == int[].class) {
-                    final ExportedProperty property = sAnnotations.get(field);
                     final int[] array = (int[]) field.get(view);
-                    final String valuePrefix = prefix + field.getName() + '_';
+                    final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
                     final String suffix = "";
 
                     exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
@@ -1464,10 +1494,9 @@
                     // We exit here!
                     return;
                 } else if (!type.isPrimitive()) {
-                    final ExportedProperty property = sAnnotations.get(field);
                     if (property.deepExport()) {
-                        dumpViewProperties(context, field.get(view), out,
-                                prefix + property.prefix());
+                        dumpViewProperties(context, field.get(view), out, prefix
+                                + property.prefix());
                         continue;
                     }
                 }
@@ -1476,7 +1505,7 @@
                     fieldValue = field.get(view);
                 }
 
-                writeEntry(out, prefix, field.getName(), "", fieldValue);
+                writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
             } catch (IllegalAccessException e) {
             }
         }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9da5637..e2f9c15 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -377,7 +377,7 @@
      * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS},
      *   {@link #FOCUS_BLOCK_DESCENDANTS}.
      */
-    @ViewDebug.ExportedProperty(mapping = {
+    @ViewDebug.ExportedProperty(category = "focus", mapping = {
         @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"),
         @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"),
         @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS")
@@ -905,19 +905,16 @@
         }
 
         // Calculate the offset point into the target's local coordinates
-        float xc;
-        float yc;
-        if (target.hasIdentityMatrix() || mAttachInfo == null) {
-            xc = scrolledXFloat - (float) target.mLeft;
-            yc = scrolledYFloat - (float) target.mTop;
-        } else {
+        float xc = scrolledXFloat - (float) target.mLeft;
+        float yc = scrolledYFloat - (float) target.mTop;
+        if (!target.hasIdentityMatrix() && mAttachInfo != null) {
             // non-identity matrix: transform the point into the view's coordinates
             final float[] localXY = mAttachInfo.mTmpTransformLocation;
-            localXY[0] = scrolledXFloat;
-            localXY[1] = scrolledYFloat;
+            localXY[0] = xc;
+            localXY[1] = yc;
             target.getInverseMatrix().mapPoints(localXY);
-            xc = localXY[0] - (float) target.mLeft;
-            yc = localXY[1] - (float) target.mTop;
+            xc = localXY[0];
+            yc = localXY[1];
         }
 
         // if have a target, see if we're allowed to and want to intercept its
@@ -2835,7 +2832,7 @@
      * @see #setChildrenDrawnWithCacheEnabled(boolean)
      * @see View#setDrawingCacheEnabled(boolean)
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     public boolean isAlwaysDrawnWithCacheEnabled() {
         return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE;
     }
@@ -2870,7 +2867,7 @@
      * @see #setAlwaysDrawnWithCacheEnabled(boolean)
      * @see #setChildrenDrawnWithCacheEnabled(boolean)
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     protected boolean isChildrenDrawnWithCacheEnabled() {
         return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE;
     }
@@ -2902,7 +2899,7 @@
      * @see #setChildrenDrawingOrderEnabled(boolean)
      * @see #getChildDrawingOrder(int, int)
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     protected boolean isChildrenDrawingOrderEnabled() {
         return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
     }
@@ -2939,7 +2936,7 @@
      *         {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE}
      *         and {@link #PERSISTENT_ALL_CACHES}
      */
-    @ViewDebug.ExportedProperty(mapping = {
+    @ViewDebug.ExportedProperty(category = "drawing", mapping = {
         @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE,        to = "NONE"),
         @ViewDebug.IntToString(from = PERSISTENT_ANIMATION_CACHE, to = "ANIMATION"),
         @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"),
@@ -3572,7 +3569,7 @@
          * constants FILL_PARENT (replaced by MATCH_PARENT ,
          * in API Level 8) or WRAP_CONTENT. or an exact size.
          */
-        @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
             @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
             @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
         })
@@ -3583,7 +3580,7 @@
          * constants FILL_PARENT (replaced by MATCH_PARENT ,
          * in API Level 8) or WRAP_CONTENT. or an exact size.
          */
-        @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
             @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
             @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
         })
@@ -3708,25 +3705,25 @@
         /**
          * The left margin in pixels of the child.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public int leftMargin;
 
         /**
          * The top margin in pixels of the child.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public int topMargin;
 
         /**
          * The right margin in pixels of the child.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public int rightMargin;
 
         /**
          * The bottom margin in pixels of the child.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public int bottomMargin;
 
         /**
diff --git a/core/java/android/webkit/DeviceOrientationManager.java b/core/java/android/webkit/DeviceOrientationManager.java
new file mode 100644
index 0000000..778b043
--- /dev/null
+++ b/core/java/android/webkit/DeviceOrientationManager.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.util.Log;
+
+/**
+ * This class is simply a container for the methods used to configure WebKit's
+ * mock DeviceOrientationClient for use in LayoutTests.
+ *
+ * This could be part of WebViewCore, but have moved it to its own class to
+ * avoid bloat there.
+ * @hide
+ */
+public final class DeviceOrientationManager {
+    /**
+     * Sets whether the Page for the specified WebViewCore should use a mock DeviceOrientation
+     * client.
+     */
+    public static void useMock(WebViewCore webViewCore) {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        nativeUseMock(webViewCore);
+    }
+
+    /**
+     * Set the position for the mock DeviceOrientation service for the supplied WebViewCore.
+     */
+    public static void setMockOrientation(WebViewCore webViewCore, boolean canProvideAlpha,
+            double alpha, boolean canProvideBeta, double beta, boolean canProvideGamma,
+            double gamma) {
+        assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
+        nativeSetMockOrientation(webViewCore, canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
+    // Native functions
+    private static native void nativeUseMock(WebViewCore webViewCore);
+    private static native void nativeSetMockOrientation(WebViewCore webViewCore,
+            boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
+            boolean canProvideGamma, double gamma);
+}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index d1b0902..44f036b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -66,6 +66,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.webkit.DeviceOrientationManager;
 import android.webkit.WebTextView.AutoCompleteAdapter;
 import android.webkit.WebViewCore.EventHub;
 import android.webkit.WebViewCore.TouchEventData;
@@ -3747,6 +3748,26 @@
     }
 
     /**
+     * Called by DRT on UI thread, need to proxy to WebCore thread.
+     *
+     * @hide debug only
+     */
+    public void useMockDeviceOrientation() {
+        mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
+    }
+
+    /**
+     * Called by DRT on WebCore thread.
+     *
+     * @hide debug only
+     */
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        DeviceOrientationManager.setMockOrientation(mWebViewCore, canProvideAlpha, alpha,
+                canProvideBeta, beta, canProvideGamma, gamma);
+    }
+
+    /**
      * Dump the V8 counters to standard output.
      * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to
      * true. Otherwise, this will do nothing.
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 3c28c94..9ec97cd 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -33,6 +33,7 @@
 import android.view.KeyEvent;
 import android.view.SurfaceView;
 import android.view.View;
+import android.webkit.DeviceOrientationManager;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -878,6 +879,8 @@
         // accessibility support
         static final int MODIFY_SELECTION = 190;
 
+        static final int USE_MOCK_DEVICE_ORIENTATION = 191;
+
         // private message ids
         private static final int DESTROY =     200;
 
@@ -1409,6 +1412,10 @@
                                     WebView.SET_TOUCH_HIGHLIGHT_RECTS, null)
                                     .sendToTarget();
                             break;
+
+                        case USE_MOCK_DEVICE_ORIENTATION:
+                            useMockDeviceOrientation();
+                            break;
                     }
                 }
             };
@@ -2481,6 +2488,10 @@
                 hMode, vMode).sendToTarget();
     }
 
+    private void useMockDeviceOrientation() {
+        DeviceOrientationManager.useMock(this);
+    }
+
     private native void nativePause();
     private native void nativeResume();
     private native void nativeFreeMemory();
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index d75d421..d7b4452 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -772,6 +772,9 @@
     }
 
     long getCacheTotalSize() {
+        if (mCacheDatabase == null) {
+            return 0;
+        }
         long size = 0;
         Cursor cursor = null;
         final String query = "SELECT SUM(contentlength) as sum FROM cache";
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 372cc83..e572d3d 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3998,7 +3998,7 @@
          * View type for this view, as returned by
          * {@link android.widget.Adapter#getItemViewType(int) }
          */
-        @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.ExportedProperty(category = "list", mapping = {
             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"),
             @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER")
         })
@@ -4010,7 +4010,7 @@
          * been added to the list view and whether they should be treated as
          * recycled views or not.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "list")
         boolean recycledHeaderFooter;
 
         /**
@@ -4021,7 +4021,7 @@
          * view to be attached to the window rather than just attached to the
          * parent.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "list")
         boolean forceAdd;
 
         public LayoutParams(Context c, AttributeSet attrs) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index fe6d91a..10a8729 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -56,7 +56,7 @@
     /**
      * The position of the first child displayed
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "scrolling")
     int mFirstPosition = 0;
 
     /**
@@ -141,7 +141,7 @@
      * The position within the adapter's data set of the item to select
      * during the next layout.
      */
-    @ViewDebug.ExportedProperty    
+    @ViewDebug.ExportedProperty(category = "list")
     int mNextSelectedPosition = INVALID_POSITION;
 
     /**
@@ -152,7 +152,7 @@
     /**
      * The position within the adapter's data set of the currently selected item.
      */
-    @ViewDebug.ExportedProperty    
+    @ViewDebug.ExportedProperty(category = "list")
     int mSelectedPosition = INVALID_POSITION;
 
     /**
@@ -168,7 +168,7 @@
     /**
      * The number of items in the current adapter.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "list")
     int mItemCount;
 
     /**
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index a6d51702..2b723c9 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -16,14 +16,22 @@
 
 package android.widget;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+
+import android.animation.PropertyAnimator;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
@@ -35,23 +43,100 @@
  * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
  * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
  */
-public class AdapterViewAnimator extends AdapterView<Adapter> implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
+public abstract class AdapterViewAnimator extends AdapterView<Adapter>
+        implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
     private static final String TAG = "RemoteViewAnimator";
 
+    /**
+     * The index of the current child, which appears anywhere from the beginning
+     * to the end of the current set of children, as specified by {@link #mActiveOffset}
+     */
     int mWhichChild = 0;
-    boolean mFirstTime = true;
+
+    /**
+     * Whether or not the first view(s) should be animated in
+     */
     boolean mAnimateFirstTime = true;
 
+    /**
+     *  Represents where the in the current window of
+     *  views the current <code>mDisplayedChild</code> sits
+     */
+    int mActiveOffset = 0;
+
+    /**
+     * The number of views that the {@link AdapterViewAnimator} keeps as children at any
+     * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
+     */
+    int mNumActiveViews = 1;
+
+    /**
+     * Array of the children of the {@link AdapterViewAnimator}. This array
+     * is accessed in a circular fashion
+     */
+    View[] mActiveViews;
+
+    /**
+     * List of views pending removal from the {@link AdapterViewAnimator}
+     */
+    ArrayList<View> mPreviousViews;
+
+    /**
+     * The index, relative to the adapter, of the beginning of the window of views
+     */
+    int mCurrentWindowStart = 0;
+
+    /**
+     * The index, relative to the adapter, of the end of the window of views
+     */
+    int mCurrentWindowEnd = -1;
+
+    /**
+     * The same as {@link #mCurrentWindowStart}, except when the we have bounded
+     * {@link #mCurrentWindowStart} to be non-negative
+     */
+    int mCurrentWindowStartUnbounded = 0;
+
+    /**
+     * Indicates whether to treat the adapter to be a circular structure, ie.
+     * the view before 0 is considered to be <code>mAdapter.getCount() - 1</code>
+     *
+     * TODO: this doesn't do anything yet
+     *
+     */
+    boolean mCycleViews = false;
+
+    /**
+     * Handler to post events to the main thread
+     */
+    Handler mMainQueue;
+
+    /**
+     * Listens for data changes from the adapter
+     */
     AdapterDataSetObserver mDataSetObserver;
 
-    View mPreviousView;
-    View mCurrentView;
+    /**
+     * The {@link Adapter} for this {@link AdapterViewAnimator}
+     */
+    Adapter mAdapter;
 
+    /**
+     * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
+     */
+    RemoteViewsAdapter mRemoteViewsAdapter;
+
+    /**
+     * Specifies whether this is the first time the animator is showing views
+     */
+    boolean mFirstTime = true;
+
+    /**
+     * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
+     */
     Animation mInAnimation;
     Animation mOutAnimation;
-    Adapter mAdapter;
-    RemoteViewsAdapter mRemoteViewsAdapter;
-    private Handler mMainQueue;
+    private  ArrayList<View> mViewsToBringToFront;
 
     public AdapterViewAnimator(Context context) {
         super(context);
@@ -61,8 +146,10 @@
     public AdapterViewAnimator(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewAnimator);
-        int resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.ViewAnimator);
+        int resource = a.getResourceId(
+                com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
         if (resource > 0) {
             setInAnimation(context, resource);
         }
@@ -72,7 +159,8 @@
             setOutAnimation(context, resource);
         }
 
-        boolean flag = a.getBoolean(com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
+        boolean flag = a.getBoolean(
+                com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
         setAnimateFirstView(flag);
 
         a.recycle();
@@ -85,6 +173,54 @@
      */
     private void initViewAnimator(Context context, AttributeSet attrs) {
         mMainQueue = new Handler(Looper.myLooper());
+        mActiveViews = new View[mNumActiveViews];
+        mPreviousViews = new ArrayList<View>();
+        mViewsToBringToFront = new ArrayList<View>();
+    }
+
+    /**
+     * This method is used by subclasses to configure the animator to display the
+     * desired number of views, and specify the offset
+     *
+     * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
+     * @param activeOffset This parameter specifies where the current index ({@link mWhichChild})
+     *        sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
+     *        and {@link setDisplayedChild} is called with 10, then the effective window will be
+     *        the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
+     *        window would instead contain indexes 10, 11 and 12.
+     */
+     void configureViewAnimator(int numVisibleViews, int activeOffset) {
+        if (activeOffset > numVisibleViews - 1) {
+            // Throw an exception here.
+        }
+        mNumActiveViews = numVisibleViews;
+        mActiveOffset = activeOffset;
+        mActiveViews = new View[mNumActiveViews];
+        mPreviousViews.clear();
+        removeAllViewsInLayout();
+        mCurrentWindowStart = 0;
+        mCurrentWindowEnd = -1;
+    }
+
+    /**
+     * This class should be overridden by subclasses to customize view transitions within
+     * the set of visible views
+     *
+     * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
+     *        in the window
+     * @param toIndex The relative index within the window that the view is going to, -1 if it is
+     *        being removed
+     * @param view The view that is being animated
+     */
+    void animateViewForTransition(int fromIndex, int toIndex, View view) {
+        PropertyAnimator pa;
+        if (fromIndex == -1) {
+            pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f);
+            pa.start();
+        } else if (toIndex == -1) {
+            pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f);
+            pa.start();
+        }
     }
 
     /**
@@ -114,18 +250,28 @@
     /**
      * Return default inAnimation. To be overriden by subclasses.
      */
-    public Animation getDefaultInAnimation() {
+    Animation getDefaultInAnimation() {
         return null;
     }
 
     /**
-     * Return default outAnimation. To be overriden by subclasses.
+     * Return default outAnimation. To be overridden by subclasses.
      */
-    public Animation getDefaultOutAnimation() {
+    Animation getDefaultOutAnimation() {
         return null;
     }
 
     /**
+     * To be overridden by subclasses. This method applies a view / index specific
+     * transform to the child view.
+     *
+     * @param child
+     * @param relativeIndex
+     */
+    void applyTransformForChildAtIndex(View child, int relativeIndex) {
+    }
+
+    /**
      * Returns the index of the currently displayed child view.
      */
     public int getDisplayedChild() {
@@ -160,70 +306,137 @@
         showOnly(childIndex, animate, false);
     }
 
-    private LayoutParams makeLayoutParams() {
-        int width = mMeasuredWidth - mPaddingLeft - mPaddingRight;
-        int height = mMeasuredHeight - mPaddingTop - mPaddingBottom;
-        return new LayoutParams(width, height);
+    private int modulo(int pos, int size) {
+        return (size + (pos % size)) % size;
     }
 
-    protected void showOnly(int childIndex, boolean animate, boolean onLayout) {
-        if (mAdapter != null) {
-            // The previous view should be removed from the ViewGroup
-            if (mPreviousView != null) {
-                mPreviousView.clearAnimation();
+    /**
+     * Get the view at this index relative to the current window's start
+     *
+     * @param relativeIndex Position relative to the current window's start
+     * @return View at this index, null if the index is outside the bounds
+     */
+    View getViewAtRelativeIndex(int relativeIndex) {
+        if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1) {
+            int index = mCurrentWindowStartUnbounded + relativeIndex;
+            return mActiveViews[modulo(index, mNumActiveViews)];
+        }
+        return null;
+    }
 
-                // TODO: this is where we would store the the view for
-                // recycling
-                removeViewInLayout(mPreviousView);
-            }
+    private LayoutParams createOrReuseLayoutParams(View v) {
+        final LayoutParams currentLp = (LayoutParams) v.getLayoutParams();
+        if (currentLp instanceof LayoutParams) {
+            return currentLp;
+        }
+        return new LayoutParams(v);
+    }
 
-            // If the current view is still being animated, we should
-            // force the animation to end
-            if (mCurrentView != null) {
-                mCurrentView.clearAnimation();
-            }
+    void showOnly(int childIndex, boolean animate, boolean onLayout) {
+        if (mAdapter == null) return;
 
-            // load the new mCurrentView from our adapter
-            mPreviousView = mCurrentView;
-            mCurrentView = mAdapter.getView(childIndex, null, this);
-            if (mPreviousView != mCurrentView) {
-                addViewInLayout(mCurrentView, 0, makeLayoutParams(), true);
-                mCurrentView.bringToFront();
-            }
+        for (int i = 0; i < mPreviousViews.size(); i++) {
+            View viewToRemove = mPreviousViews.get(i);
+            viewToRemove.clearAnimation();
+            // applyTransformForChildAtIndex here just allows for any cleanup
+            // associated with this view that may need to be done by a subclass
+            applyTransformForChildAtIndex(viewToRemove, -1);
+            removeViewInLayout(viewToRemove);
+        }
+        mPreviousViews.clear();
+        int newWindowStartUnbounded = childIndex - mActiveOffset;
+        int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
+        int newWindowStart = Math.max(0, newWindowStartUnbounded);
+        int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded);
 
-
-
-            // Animate as necessary
-            if (mPreviousView != null && mPreviousView != mCurrentView) {
-                if (animate && mOutAnimation != null) {
-                    mPreviousView.startAnimation(mOutAnimation);
+        // This section clears out any items that are in our mActiveViews list
+        // but are outside the effective bounds of our window (this is becomes an issue
+        // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
+        // newWindowEndUnbounded > mAdapter.getCount() - 1
+        for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) {
+            if (i < newWindowStart || i > newWindowEnd) {
+                int index = modulo(i, mNumActiveViews);
+                if (mActiveViews[index] != null) {
+                    View previousView = mActiveViews[index];
+                    mPreviousViews.add(previousView);
+                    int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
+                            mNumActiveViews);
+                    animateViewForTransition(previousViewRelativeIndex, -1, previousView);
                 }
-                // This line results in the view becoming invisible *after*
-                // the above animation is complete, or, if there is no animation
-                // then it becomes invisble immediately
-                mPreviousView.setVisibility(View.GONE);
             }
+        }
 
-            if (mCurrentView != null && animate && mInAnimation != null) {
-                mCurrentView.startAnimation(mInAnimation);
-            }
+        // If the window has changed
+        if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
+            // Run through the indices in the new range
+            for (int i = newWindowStart; i <= newWindowEnd; i++) {
 
-            mFirstTime = false;
-            if (!onLayout) {
-                requestLayout();
-                invalidate();
-            } else {
-                // If the Adapter tries to layout the current view when we get it using getView above
-                // the layout will end up being ignored since we are currently laying out, so
-                // we post a delayed requestLayout and invalidate
-                mMainQueue.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mCurrentView.requestLayout();
-                        mCurrentView.invalidate();
+                int oldRelativeIndex = i - mCurrentWindowStartUnbounded;
+                int newRelativeIndex = i - newWindowStartUnbounded;
+                int index = modulo(i, mNumActiveViews);
+
+                // If this item is in the current window, great, we just need to apply
+                // the transform for it's new relative position in the window, and animate
+                // between it's current and new relative positions
+                if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) {
+                    View view = mActiveViews[index];
+                    applyTransformForChildAtIndex(view, newRelativeIndex);
+                    animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
+
+                // Otherwise this view is new, so first we have to displace the view that's
+                // taking the new view's place within our cache (a circular array)
+                } else {
+                    if (mActiveViews[index] != null) {
+                        View previousView = mActiveViews[index];
+                        mPreviousViews.add(previousView);
+                        int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
+                                mNumActiveViews);
+                        animateViewForTransition(previousViewRelativeIndex, -1, previousView);
+
+                        if (mCurrentWindowStart > newWindowStart) {
+                            mViewsToBringToFront.add(previousView);
+                        }
                     }
-                });
+
+                    // We've cleared a spot for the new view. Get it from the adapter, add it
+                    // and apply any transform / animation
+                    View newView = mAdapter.getView(i, null, this);
+                    if (newView != null) {
+                        mActiveViews[index] = newView;
+                        addViewInLayout(newView, -1, createOrReuseLayoutParams(newView));
+                        applyTransformForChildAtIndex(newView, newRelativeIndex);
+                        animateViewForTransition(-1, newRelativeIndex, newView);
+                    }
+                }
+                mActiveViews[index].bringToFront();
             }
+
+            for (int i = 0; i < mViewsToBringToFront.size(); i++) {
+                View v = mViewsToBringToFront.get(i);
+                v.bringToFront();
+            }
+            mViewsToBringToFront.clear();
+
+            mCurrentWindowStart = newWindowStart;
+            mCurrentWindowEnd = newWindowEnd;
+            mCurrentWindowStartUnbounded = newWindowStartUnbounded;
+        }
+
+        mFirstTime = false;
+        if (!onLayout) {
+            requestLayout();
+            invalidate();
+        } else {
+            // If the Adapter tries to layout the current view when we get it using getView
+            // above the layout will end up being ignored since we are currently laying out, so
+            // we post a delayed requestLayout and invalidate
+            mMainQueue.post(new Runnable() {
+                @Override
+                public void run() {
+                    requestLayout();
+                    invalidate();
+                }
+            });
         }
     }
 
@@ -247,10 +460,11 @@
 
             int childRight = mPaddingLeft + child.getMeasuredWidth();
             int childBottom = mPaddingTop + child.getMeasuredHeight();
+            LayoutParams lp = (LayoutParams) child.getLayoutParams();
 
-            child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom);
+            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
+                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
         }
-
         mDataChanged = false;
     }
 
@@ -261,8 +475,6 @@
         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 
-        Log.v(TAG, "onMeasure");
-
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
 
@@ -278,7 +490,6 @@
 
             child.measure(childWidthMeasureSpec, childheightMeasureSpec);
         }
-
         setMeasuredDimension(widthSpecSize, heightSpecSize);
     }
 
@@ -302,7 +513,7 @@
      * @see #getDisplayedChild()
      */
     public View getCurrentView() {
-        return mCurrentView;
+        return getViewAtRelativeIndex(mActiveOffset);
     }
 
     /**
@@ -402,22 +613,25 @@
 
     @Override
     public void setAdapter(Adapter adapter) {
+        if (mAdapter != null && mDataSetObserver != null) {
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
+        }
+
         mAdapter = adapter;
 
         if (mAdapter != null) {
-            if (mDataSetObserver != null) {
-                mAdapter.unregisterDataSetObserver(mDataSetObserver);
-            }
-
             mDataSetObserver = new AdapterDataSetObserver();
             mAdapter.registerDataSetObserver(mDataSetObserver);
         }
+        setFocusable(true);
     }
 
     /**
-     * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a RemoteViewsService
-     * through the specified intent.
-     * @param intent the intent used to identify the RemoteViewsService for the adapter to connect to.
+     * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
+     * RemoteViewsService through the specified intent.
+     *
+     * @param intent the intent used to identify the RemoteViewsService for the adapter to
+     *        connect to.
      */
     @android.view.RemotableViewMethod
     public void setRemoteViewsAdapter(Intent intent) {
@@ -431,7 +645,7 @@
 
     @Override
     public View getSelectedView() {
-        return mCurrentView;
+        return getViewAtRelativeIndex(mActiveOffset);
     }
 
     /**
@@ -452,4 +666,61 @@
             setAdapter(mRemoteViewsAdapter);
         }
     }
+
+    static class LayoutParams extends ViewGroup.LayoutParams {
+        int horizontalOffset;
+        int verticalOffset;
+        View mView;
+
+        LayoutParams(View view) {
+            super(0, 0);
+            horizontalOffset = 0;
+            verticalOffset = 0;
+            mView = view;
+        }
+
+        LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+            horizontalOffset = 0;
+            verticalOffset = 0;
+        }
+
+        void setHorizontalOffset(int newHorizontalOffset) {
+            horizontalOffset = newHorizontalOffset;
+            if (mView != null) {
+                mView.requestLayout();
+                mView.invalidate();
+            }
+        }
+
+        private Rect parentRect = new Rect();
+        void invalidateGlobalRegion(View v, Rect r) {
+            View p = v;
+            boolean firstPass = true;
+            parentRect.set(0, 0, 0, 0);
+            while (p.getParent() != null && p.getParent() instanceof View
+                    && !parentRect.contains(r)) {
+                if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
+                firstPass = false;
+                p = (View) p.getParent();
+                parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
+                        p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
+            }
+            p.invalidate(r.left, r.top, r.right, r.bottom);
+        }
+
+        private Rect invalidateRect = new Rect();
+        // This is public so that PropertyAnimator can access it
+        public void setVerticalOffset(int newVerticalOffset) {
+            int offsetDelta = newVerticalOffset - verticalOffset;
+            verticalOffset = newVerticalOffset;
+            if (mView != null) {
+                mView.requestLayout();
+                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
+                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
+                invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom);
+                invalidateGlobalRegion(mView, invalidateRect);
+            }
+        }
+    }
 }
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index e27bb4fe..e445180 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -46,27 +46,32 @@
  */
 @RemoteView
 public class FrameLayout extends ViewGroup {
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     boolean mMeasureAllChildren = false;
 
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     private Drawable mForeground;
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "padding")
     private int mForegroundPaddingLeft = 0;
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "padding")
     private int mForegroundPaddingTop = 0;
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "padding")
     private int mForegroundPaddingRight = 0;
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "padding")
     private int mForegroundPaddingBottom = 0;
 
     private final Rect mSelfBounds = new Rect();
     private final Rect mOverlayBounds = new Rect();
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "drawing")
     private int mForegroundGravity = Gravity.FILL;
 
     /** {@hide} */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "drawing")
     protected boolean mForegroundInPadding = true;
 
     boolean mForegroundBoundsChanged = false;
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 7254c3c..53187bf 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -57,7 +57,7 @@
      * Whether the children of this layout are baseline aligned.  Only applicable
      * if {@link #mOrientation} is horizontal.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     private boolean mBaselineAligned = true;
 
     /**
@@ -67,7 +67,7 @@
      * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned
      * with whether the children of this layout are baseline aligned.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     private int mBaselineAlignedChildIndex = -1;
 
     /**
@@ -75,12 +75,13 @@
      * We'll calculate the baseline of this layout as we measure vertically; for
      * horizontal linear layouts, the offset of 0 is appropriate.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     private int mBaselineChildTop = 0;
 
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "measurement")
     private int mOrientation;
-    @ViewDebug.ExportedProperty(mapping = {
+
+    @ViewDebug.ExportedProperty(category = "measurement", mapping = {
             @ViewDebug.IntToString(from =  -1,                       to = "NONE"),
             @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,        to = "NONE"),
             @ViewDebug.IntToString(from = Gravity.TOP,               to = "TOP"),
@@ -95,13 +96,14 @@
             @ViewDebug.IntToString(from = Gravity.FILL,              to = "FILL")
         })
     private int mGravity = Gravity.LEFT | Gravity.TOP;
-    @ViewDebug.ExportedProperty
+
+    @ViewDebug.ExportedProperty(category = "measurement")
     private int mTotalLength;
 
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     private float mWeightSum;
 
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "layout")
     private boolean mUseLargestChild;
 
     private int[] mMaxAscent;
@@ -1401,7 +1403,7 @@
          * 0 if the view should not be stretched. Otherwise the extra pixels
          * will be pro-rated among all views whose weight is greater than 0.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public float weight;
 
         /**
@@ -1409,7 +1411,7 @@
          *
          * @see android.view.Gravity
          */
-        @ViewDebug.ExportedProperty(mapping = {
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
             @ViewDebug.IntToString(from =  -1,                       to = "NONE"),
             @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,        to = "NONE"),
             @ViewDebug.IntToString(from = Gravity.TOP,               to = "TOP"),
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 0bb41e5..0d0a1ba 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -1186,7 +1186,7 @@
      *         UNSPECIFIED/AT_MOST modes, false otherwise.
      * @hide
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "list")
     protected boolean recycleOnMeasure() {
         return true;
     }
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index d404ce7..b562942 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -122,7 +122,7 @@
     private OnScrollChangedListener mOnScrollChangedListener =
         new OnScrollChangedListener() {
             public void onScrollChanged() {
-                View anchor = mAnchor.get();
+                View anchor = mAnchor != null ? mAnchor.get() : null;
                 if (anchor != null && mPopupView != null) {
                     WindowManager.LayoutParams p = (WindowManager.LayoutParams)
                             mPopupView.getLayoutParams();
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 71f0c2f..0f52fc8 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -336,7 +336,7 @@
      *
      * @return true if the progress bar is in indeterminate mode
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "progress")
     public synchronized boolean isIndeterminate() {
         return mIndeterminate;
     }
@@ -609,7 +609,7 @@
      * @see #setMax(int)
      * @see #getMax()
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "progress")
     public synchronized int getProgress() {
         return mIndeterminate ? 0 : mProgress;
     }
@@ -626,7 +626,7 @@
      * @see #setMax(int)
      * @see #getMax()
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "progress")
     public synchronized int getSecondaryProgress() {
         return mIndeterminate ? 0 : mSecondaryProgress;
     }
@@ -640,7 +640,7 @@
      * @see #getProgress()
      * @see #getSecondaryProgress()
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "progress")
     public synchronized int getMax() {
         return mMax;
     }
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 1aa1df3..64cda49 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -1011,7 +1011,7 @@
      * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical
      */
     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        @ViewDebug.ExportedProperty(resolveId = true, indexMapping = {
+        @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = {
             @ViewDebug.IntToString(from = ABOVE,               to = "above"),
             @ViewDebug.IntToString(from = ALIGN_BASELINE,      to = "alignBaseline"),
             @ViewDebug.IntToString(from = ALIGN_BOTTOM,        to = "alignBottom"),
@@ -1040,7 +1040,7 @@
          * When true, uses the parent as the anchor if the anchor doesn't exist or if
          * the anchor's visibility is GONE.
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public boolean alignWithParent;
 
         public LayoutParams(Context c, AttributeSet attrs) {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 52635e8..ebf5d6e 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -433,6 +433,7 @@
                     int cacheIndex = getCacheIndex(position);
                     FrameLayout flipper = mViewCache[cacheIndex].flipper;
                     flipper.setVisibility(View.VISIBLE);
+                    flipper.setAlpha(1.0f);
 
                     if (indexInfo == null) {
                         // hide the item view and show the loading view
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index c9c217a..aee411e 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -36,9 +36,8 @@
     
     /**
      * Constructor.
-     * 
-     * @param context The context where the ListView associated with this
-     *            SimpleListItemFactory is running
+     *
+     * @param context The context where the ListView associated with this adapter is running
      * @param layout resource identifier of a layout file that defines the views
      *            for this list item.  Unless you override them later, this will
      *            define both the item views and the drop down views.
@@ -51,9 +50,8 @@
     
     /**
      * Constructor.
-     * 
-     * @param context The context where the ListView associated with this
-     *            SimpleListItemFactory is running
+     *
+     * @param context The context where the ListView associated with this adapter is running
      * @param layout resource identifier of a layout file that defines the views
      *            for this list item.  Unless you override them later, this will
      *            define both the item views and the drop down views.
@@ -69,6 +67,22 @@
     }
 
     /**
+     * Constructor.
+     *
+     * @param context The context where the ListView associated with this adapter is running
+     * @param layout resource identifier of a layout file that defines the views
+     *            for this list item.  Unless you override them later, this will
+     *            define both the item views and the drop down views.
+     * @param c The cursor from which to get the data.
+     * @param flags flags used to determine the behavior of the adapter
+     */
+    public ResourceCursorAdapter(Context context, int layout, Cursor c, int flags) {
+        super(context, c, flags);
+        mLayout = mDropDownLayout = layout;
+        mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+    }
+
+    /**
      * Inflates view(s) from the specified XML file.
      * 
      * @see android.widget.CursorAdapter#newView(android.content.Context,
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
new file mode 100644
index 0000000..4cd44d9
--- /dev/null
+++ b/core/java/android/widget/StackView.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import java.util.WeakHashMap;
+
+import android.animation.PropertyAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.RemoteView;
+
+@RemoteView
+/**
+ * A view that displays its children in a stack and allows users to discretely swipe
+ * through the children.
+ */
+public class StackView extends AdapterViewAnimator {
+    private final String TAG = "StackView";
+
+    /**
+     * Default animation parameters
+     */
+    private final int DEFAULT_ANIMATION_DURATION = 400;
+    private final int MINIMUM_ANIMATION_DURATION = 50;
+
+    /**
+     * These specify the different gesture states
+     */
+    private final int GESTURE_NONE = 0;
+    private final int GESTURE_SLIDE_UP = 1;
+    private final int GESTURE_SLIDE_DOWN = 2;
+
+    /**
+     * Specifies how far you need to swipe (up or down) before it
+     * will be consider a completed gesture when you lift your finger
+     */
+    private final float SWIPE_THRESHOLD_RATIO = 0.35f;
+    private final float SLIDE_UP_RATIO = 0.7f;
+
+    private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>();
+    private final WeakHashMap<View, Integer>
+            mChildrenToApplyTransformsTo = new WeakHashMap<View, Integer>();
+
+    /**
+     * Sentinel value for no current active pointer.
+     * Used by {@link #mActivePointerId}.
+     */
+    private static final int INVALID_POINTER = -1;
+
+    /**
+     * These variables are all related to the current state of touch interaction
+     * with the stack
+     */
+    private boolean mGestureComplete = false;
+    private float mInitialY;
+    private float mInitialX;
+    private int mActivePointerId;
+    private int mYOffset = 0;
+    private int mYVelocity = 0;
+    private int mSwipeGestureType = GESTURE_NONE;
+    private int mViewHeight;
+    private int mSwipeThreshold;
+    private int mTouchSlop;
+    private int mMaximumVelocity;
+    private VelocityTracker mVelocityTracker;
+
+    private boolean mFirstLayoutHappened = false;
+
+    // TODO: temp hack to get this thing started
+    int mIndex = 5;
+
+    public StackView(Context context) {
+        super(context);
+        initStackView();
+    }
+
+    public StackView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initStackView();
+    }
+
+    private void initStackView() {
+        configureViewAnimator(4, 2);
+        setStaticTransformationsEnabled(true);
+        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        mTouchSlop = configuration.getScaledTouchSlop();// + 5;
+        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mActivePointerId = INVALID_POINTER;
+    }
+
+    /**
+     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
+     */
+    void animateViewForTransition(int fromIndex, int toIndex, View view) {
+        if (fromIndex == -1 && toIndex == 0) {
+            // Fade item in
+            if (view.getAlpha() == 1) {
+                view.setAlpha(0);
+            }
+            PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
+                    view, "alpha", view.getAlpha(), 1.0f);
+            fadeIn.start();
+        } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
+            // Slide item in
+            view.setVisibility(VISIBLE);
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+
+            int largestDuration = (int) Math.round(
+                    (lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION);
+            int duration = largestDuration;
+            if (mYVelocity != 0) {
+                duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity);
+            }
+
+            duration = Math.min(duration, largestDuration);
+            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
+
+            PropertyAnimator slideDown = new PropertyAnimator(duration, lp,
+                    "verticalOffset", lp.verticalOffset, 0);
+            slideDown.start();
+
+            PropertyAnimator fadeIn = new PropertyAnimator(duration, view,
+                    "alpha", view.getAlpha(), 1.0f);
+            fadeIn.start();
+        } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
+            // Slide item out
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+
+            int largestDuration = (int) Math.round(
+                    (1 - (lp.verticalOffset*1.0f/-mViewHeight))*DEFAULT_ANIMATION_DURATION);
+            int duration = largestDuration;
+            if (mYVelocity != 0) {
+                duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity);
+            }
+
+            duration = Math.min(duration, largestDuration);
+            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
+
+            PropertyAnimator slideUp = new PropertyAnimator(duration, lp,
+                    "verticalOffset", lp.verticalOffset, -mViewHeight);
+            slideUp.start();
+
+            PropertyAnimator fadeOut = new PropertyAnimator(duration, view,
+                    "alpha", view.getAlpha(), 0.0f);
+            fadeOut.start();
+        } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
+            // Make sure this view that is "waiting in the wings" is invisible
+            view.setAlpha(0.0f);
+        } else if (toIndex == -1) {
+            // Fade item out
+            PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
+                    view, "alpha", view.getAlpha(), 0);
+            fadeOut.start();
+        }
+    }
+
+    /**
+     * Apply any necessary tranforms for the child that is being added.
+     */
+    void applyTransformForChildAtIndex(View child, int relativeIndex) {
+        float rotation;
+
+        if (!mRotations.containsKey(child)) {
+            rotation = (float) (Math.random()*26 - 13);
+            mRotations.put(child, rotation);
+        } else {
+            rotation = mRotations.get(child);
+        }
+
+        // Child has been removed
+        if (relativeIndex == -1) {
+            if (mRotations.containsKey(child)) {
+                mRotations.remove(child);
+            }
+            if (mChildrenToApplyTransformsTo.containsKey(child)) {
+                mChildrenToApplyTransformsTo.remove(child);
+            }
+        }
+
+        // if this view is already in the layout, we need to
+        // wait until layout has finished in order to set the
+        // pivot point of the rotation (requiring getMeasuredWidth/Height())
+        mChildrenToApplyTransformsTo.put(child, relativeIndex);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (!mChildrenToApplyTransformsTo.isEmpty()) {
+            for (View child: mChildrenToApplyTransformsTo.keySet()) {
+                if (mRotations.containsKey(child)) {
+                    child.setPivotX(child.getMeasuredWidth()/2);
+                    child.setPivotY(child.getMeasuredHeight()/2);
+                    child.setRotation(mRotations.get(child));
+                }
+            }
+            mChildrenToApplyTransformsTo.clear();
+        }
+
+        if (!mFirstLayoutHappened) {
+            mViewHeight = (int) Math.round(SLIDE_UP_RATIO*getMeasuredHeight());
+            mSwipeThreshold = (int) Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight);
+
+            // TODO: Right now this walks all the way up the view hierarchy and disables
+            // ClipChildren and ClipToPadding. We're probably going  to want to reset
+            // these flags as well.
+            setClipChildren(false);
+            ViewGroup view = this;
+            while (view.getParent() != null && view.getParent() instanceof ViewGroup) {
+                view = (ViewGroup) view.getParent();
+                view.setClipChildren(false);
+                view.setClipToPadding(false);
+            }
+
+            mFirstLayoutHappened = true;
+        }
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        switch(action & MotionEvent.ACTION_MASK) {
+
+            case MotionEvent.ACTION_DOWN: {
+                if (mActivePointerId == INVALID_POINTER) {
+                    mInitialX = ev.getX();
+                    mInitialY = ev.getY();
+                    mActivePointerId = ev.getPointerId(0);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                int pointerIndex = ev.findPointerIndex(mActivePointerId);
+                if (pointerIndex == INVALID_POINTER) {
+                    // no data for our primary pointer, this shouldn't happen, log it
+                    Log.d(TAG, "Error: No data for our primary pointer.");
+                    return false;
+                }
+
+                float newY = ev.getY(pointerIndex);
+                float deltaY = newY - mInitialY;
+
+                if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
+                    mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
+                    mGestureComplete = false;
+                    cancelLongPress();
+                    requestDisallowInterceptTouchEvent(true);
+                }
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                onSecondaryPointerUp(ev);
+                break;
+            }
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                mActivePointerId = INVALID_POINTER;
+                mSwipeGestureType = GESTURE_NONE;
+                mGestureComplete = true;
+            }
+        }
+
+        return mSwipeGestureType != GESTURE_NONE;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        int action = ev.getAction();
+        int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        if (pointerIndex == INVALID_POINTER) {
+            // no data for our primary pointer, this shouldn't happen, log it
+            Log.d(TAG, "Error: No data for our primary pointer.");
+            return false;
+        }
+
+        float newY = ev.getY(pointerIndex);
+        float deltaY = newY - mInitialY;
+
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_MOVE: {
+                if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
+                    mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
+                    mGestureComplete = false;
+                    cancelLongPress();
+                    requestDisallowInterceptTouchEvent(true);
+                }
+
+                if (!mGestureComplete) {
+                    if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
+                        View v = getViewAtRelativeIndex(mNumActiveViews - 1);
+                        if (v != null) {
+                            // This view is present but hidden, make sure it's visible
+                            // if they pull down
+                            v.setVisibility(VISIBLE);
+
+                            float r = (deltaY-mTouchSlop)*1.0f / (mSwipeThreshold);
+                            mYOffset = Math.min(-mViewHeight + (int)  Math.round(
+                                    r*mSwipeThreshold) - mTouchSlop, 0);
+                            LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                            lp.setVerticalOffset(mYOffset);
+
+                            float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight));
+                            alpha = Math.min(1.0f, alpha);
+                            v.setAlpha(alpha);
+                        }
+                        return true;
+                    } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
+                        View v = getViewAtRelativeIndex(mNumActiveViews - 2);
+
+                        if (v != null) {
+                            float r = -(deltaY*1.0f + mTouchSlop) / (mSwipeThreshold);
+                            mYOffset = Math.min((int) Math.round(r*-mSwipeThreshold), 0);
+                            LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                            lp.setVerticalOffset(mYOffset);
+
+                            float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight));
+                            alpha = Math.min(1.0f, alpha);
+                            v.setAlpha(alpha);
+                        }
+                        return true;
+                    }
+                }
+                break;
+            }
+            case MotionEvent.ACTION_UP: {
+                handlePointerUp(ev);
+                break;
+            }
+            case MotionEvent.ACTION_POINTER_UP: {
+                onSecondaryPointerUp(ev);
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL: {
+                mActivePointerId = INVALID_POINTER;
+                mGestureComplete = true;
+                mSwipeGestureType = GESTURE_NONE;
+                mYOffset = 0;
+                break;
+            }
+        }
+        return true;
+    }
+
+    private final Rect touchRect = new Rect();
+    private void onSecondaryPointerUp(MotionEvent ev) {
+        final int activePointerIndex = ev.getActionIndex();
+        final int pointerId = ev.getPointerId(activePointerIndex);
+        if (pointerId == mActivePointerId) {
+
+            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1
+                    : mNumActiveViews - 2;
+
+            View v = getViewAtRelativeIndex(activeViewIndex);
+            if (v == null) return;
+
+            // Our primary pointer has gone up -- let's see if we can find
+            // another pointer on the view. If so, then we should replace
+            // our primary pointer with this new pointer and adjust things
+            // so that the view doesn't jump
+            for (int index = 0; index < ev.getPointerCount(); index++) {
+                if (index != activePointerIndex) {
+
+                    float x = ev.getX(index);
+                    float y = ev.getY(index);
+
+                    touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
+                    if (touchRect.contains((int) Math.round(x), (int) Math.round(y))) {
+                        float oldX = ev.getX(activePointerIndex);
+                        float oldY = ev.getY(activePointerIndex);
+
+                        // adjust our frame of reference to avoid a jump
+                        mInitialY += (y - oldY);
+                        mInitialX += (x - oldX);
+
+                        mActivePointerId = ev.getPointerId(index);
+                        if (mVelocityTracker != null) {
+                            mVelocityTracker.clear();
+                        }
+                        // ok, we're good, we found a new pointer which is touching the active view
+                        return;
+                    }
+                }
+            }
+            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
+            // so end the
+            handlePointerUp(ev);
+        }
+    }
+
+    private void handlePointerUp(MotionEvent ev) {
+        int pointerIndex = ev.findPointerIndex(mActivePointerId);
+        float newY = ev.getY(pointerIndex);
+        int deltaY = (int) (newY - mInitialY);
+
+        mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+        mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+
+        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN &&
+                !mGestureComplete) {
+            // Swipe threshold exceeded, swipe down
+            showNext();
+        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP &&
+                !mGestureComplete) {
+            // Swipe threshold exceeded, swipe up
+            showPrevious();
+        } else if (mSwipeGestureType == GESTURE_SLIDE_UP && !mGestureComplete) {
+            // Didn't swipe up far enough, snap back down
+            View v = getViewAtRelativeIndex(mNumActiveViews - 2);
+            if (v != null) {
+                // Compute the animation duration based on how far they pulled it up
+                LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                int duration = (int) Math.round(
+                        lp.verticalOffset*1.0f/-mViewHeight*DEFAULT_ANIMATION_DURATION);
+                duration = Math.max(MINIMUM_ANIMATION_DURATION, duration);
+
+                // Animate back down
+                PropertyAnimator slideDown = new PropertyAnimator(duration, lp,
+                        "verticalOffset", lp.verticalOffset, 0);
+                slideDown.start();
+                PropertyAnimator fadeIn = new PropertyAnimator(duration, v,
+                        "alpha",v.getAlpha(), 1.0f);
+                fadeIn.start();
+            }
+        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN && !mGestureComplete) {
+            // Didn't swipe down far enough, snap back up
+            View v = getViewAtRelativeIndex(mNumActiveViews - 1);
+            if (v != null) {
+                // Compute the animation duration based on how far they pulled it down
+                LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                int duration = (int) Math.round(
+                        (1 - lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION);
+                duration = Math.max(MINIMUM_ANIMATION_DURATION, duration);
+
+                // Animate back up
+                PropertyAnimator slideUp = new PropertyAnimator(duration, lp,
+                        "verticalOffset", lp.verticalOffset, -mViewHeight);
+                slideUp.start();
+                PropertyAnimator fadeOut = new PropertyAnimator(duration, v,
+                        "alpha",v.getAlpha(), 0.0f);
+                fadeOut.start();
+            }
+        }
+
+        mActivePointerId = INVALID_POINTER;
+        mGestureComplete = true;
+        mSwipeGestureType = GESTURE_NONE;
+        mYOffset = 0;
+    }
+
+    @Override
+    public void onRemoteAdapterConnected() {
+        super.onRemoteAdapterConnected();
+        setDisplayedChild(mIndex);
+    }
+}
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index 48d12df..b612004 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -387,13 +387,13 @@
         /**
          * <p>The column index of the cell represented by the widget.</p>
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public int column;
 
         /**
          * <p>The number of columns the widgets spans over.</p>
          */
-        @ViewDebug.ExportedProperty
+        @ViewDebug.ExportedProperty(category = "layout")
         public int span;
 
         private static final int LOCATION = 0;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f3f68f9..b413aee 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -5818,7 +5818,7 @@
     /**
      * Convenience for {@link Selection#getSelectionStart}.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "text")
     public int getSelectionStart() {
         return Selection.getSelectionStart(getText());
     }
@@ -5826,7 +5826,7 @@
     /**
      * Convenience for {@link Selection#getSelectionEnd}.
      */
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "text")
     public int getSelectionEnd() {
         return Selection.getSelectionEnd(getText());
     }
@@ -8062,7 +8062,7 @@
     }
 
 
-    @ViewDebug.ExportedProperty
+    @ViewDebug.ExportedProperty(category = "text")
     private CharSequence            mText;
     private CharSequence            mTransformed;
     private BufferType              mBufferType = BufferType.NORMAL;
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 281f32c..6b52409 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -24,8 +24,10 @@
 
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.Dialog;
 import android.app.Fragment;
 import android.app.FragmentTransaction;
+import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
 import android.view.ActionMode;
@@ -54,7 +56,9 @@
     private static final int TAB_SWITCH_SHOW_HIDE = 0;
     private static final int TAB_SWITCH_ADD_REMOVE = 1;
 
+    private Context mContext;
     private Activity mActivity;
+    private Dialog mDialog;
 
     private ViewAnimator mAnimatorView;
     private ActionBarView mActionView;
@@ -88,8 +92,17 @@
     };
 
     public ActionBarImpl(Activity activity) {
-        final View decor = activity.getWindow().getDecorView();
         mActivity = activity;
+        init(activity.getWindow().getDecorView());
+    }
+
+    public ActionBarImpl(Dialog dialog) {
+        mDialog = dialog;
+        init(dialog.getWindow().getDecorView());
+    }
+
+    private void init(View decor) {
+        mContext = decor.getContext();
         mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
         mUpperContextView = (ActionBarContextView) decor.findViewById(
                 com.android.internal.R.id.action_context_bar);
@@ -109,23 +122,23 @@
 
     @Override
     public void setStandardNavigationMode(int titleResId, int subtitleResId) {
-        setStandardNavigationMode(mActivity.getString(titleResId),
-                mActivity.getString(subtitleResId));
+        setStandardNavigationMode(mContext.getString(titleResId),
+                mContext.getString(subtitleResId));
     }
 
     @Override
     public void setStandardNavigationMode(int titleResId) {
-        setStandardNavigationMode(mActivity.getString(titleResId));
+        setStandardNavigationMode(mContext.getString(titleResId));
     }
 
     @Override
     public void setTitle(int resId) {
-        setTitle(mActivity.getString(resId));
+        setTitle(mContext.getString(resId));
     }
 
     @Override
     public void setSubtitle(int resId) {
-        setSubtitle(mActivity.getString(resId));
+        setSubtitle(mContext.getString(resId));
     }
 
     public void setCustomNavigationMode(View view) {
@@ -345,6 +358,10 @@
 
     @Override
     public void setTabNavigationMode() {
+        if (mActivity == null) {
+            throw new IllegalStateException(
+                    "Tab navigation mode cannot be used outside of an Activity");
+        }
         mActionView.setNavigationMode(NAVIGATION_MODE_TABS);
     }
 
@@ -396,7 +413,7 @@
 
         @Override
         public MenuInflater getMenuInflater() {
-            return new MenuInflater(mActivity);
+            return new MenuInflater(mContext);
         }
 
         @Override
@@ -485,7 +502,7 @@
                 return true;
             }
 
-            new MenuPopupHelper(mActivity, subMenu).show();
+            new MenuPopupHelper(mContext, subMenu).show();
             return true;
         }
 
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
new file mode 100644
index 0000000..ce5959d
--- /dev/null
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.ImageView;
+
+public class PlatLogoActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        ImageView content = new ImageView(this);
+        content.setImageResource(com.android.internal.R.drawable.platlogo);
+        content.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        
+        setContentView(content);
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index e281536..1357251 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -24,6 +24,7 @@
 import android.widget.ImageButton;
 import android.widget.LinearLayout;
 
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
@@ -39,6 +40,7 @@
     private int mMaxItems;
     private boolean mReserveOverflow;
     private OverflowMenuButton mOverflowButton;
+    private WeakReference<MenuPopupHelper> mOverflowPopup;
     
     public ActionMenuView(Context context) {
         this(context, null);
@@ -145,6 +147,16 @@
         if (mOverflowButton != null) {
             MenuPopupHelper popup = new MenuPopupHelper(getContext(), mMenu, mOverflowButton, true);
             popup.show();
+            mOverflowPopup = new WeakReference<MenuPopupHelper>(popup);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean hideOverflowMenu() {
+        MenuPopupHelper popup = mOverflowPopup != null ? mOverflowPopup.get() : null;
+        if (popup != null) {
+            popup.dismiss();
             return true;
         }
         return false;
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index d703a2f..c641441 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -209,6 +209,13 @@
         return false;
     }
 
+    public boolean hideOverflowMenu() {
+        if (mMenuView != null) {
+            return mMenuView.hideOverflowMenu();
+        }
+        return false;
+    }
+
     public boolean isOverflowReserved() {
         return mMenuView != null && mMenuView.isOverflowReserved();
     }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index d1a5ae14..860b5b7 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -139,7 +139,8 @@
 	android_backup_BackupDataOutput.cpp \
 	android_backup_FileBackupHelperBase.cpp \
 	android_backup_BackupHelperDispatcher.cpp \
-	android_content_res_ObbScanner.cpp
+	android_content_res_ObbScanner.cpp \
+    android_content_res_Configuration.cpp
 
 LOCAL_C_INCLUDES += \
 	$(JNI_H_INCLUDE) \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8b09e5f..2dd17bb 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -167,6 +167,7 @@
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
 extern int register_android_content_res_ObbScanner(JNIEnv* env);
+extern int register_android_content_res_Configuration(JNIEnv* env);
 
 static AndroidRuntime* gCurRuntime = NULL;
 
@@ -1331,6 +1332,7 @@
     REG_JNI(register_android_view_MotionEvent),
 
     REG_JNI(register_android_content_res_ObbScanner),
+    REG_JNI(register_android_content_res_Configuration),
 };
 
 /*
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 2b98e89..cb1c333 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -149,7 +149,9 @@
     storedBounds[2] = x1; storedBounds[3] = y1;
     jfloat* storedPositions = new jfloat[count];
     uint32_t* storedColors = new uint32_t[count];
-    memcpy(storedColors, colorValues, count);
+    for (size_t i = 0; i < count; i++) {
+        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
+    }
 
     if (posArray) {
         AutoJavaFloatArray autoPos(env, posArray, count);
@@ -185,8 +187,8 @@
     storedPositions[1] = 1.0f;
 
     uint32_t* storedColors = new uint32_t[2];
-    storedColors[0] = color0;
-    storedColors[1] = color1;
+    storedColors[0] = static_cast<uint32_t>(color0);
+    storedColors[1] = static_cast<uint32_t>(color1);
 
     SkiaShader* skiaShader = new SkiaLinearGradientShader(storedBounds, storedColors,
             storedPositions, 2, shader, static_cast<SkShader::TileMode>(tileMode), NULL,
@@ -329,13 +331,8 @@
 static SkiaShader* ComposeShader_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
         SkiaShader* shaderA, SkiaShader* shaderB, SkPorterDuff::Mode porterDuffMode) {
 #ifdef USE_OPENGL_RENDERER
-    SkAutoUnref au(SkPorterDuff::CreateXfermode(porterDuffMode));
-    SkXfermode* mode = (SkXfermode*) au.get();
-    SkXfermode::Mode skiaMode;
-    if (!SkXfermode::IsMode(mode, &skiaMode)) {
-        skiaMode = SkXfermode::kSrcOver_Mode;
-    }
-    return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
+    SkXfermode::Mode mode = SkPorterDuff::ToXfermodeMode(porterDuffMode);
+    return new SkiaComposeShader(shaderA, shaderB, mode, shader);
 #else
     return NULL;
 #endif
@@ -346,6 +343,7 @@
 #ifdef USE_OPENGL_RENDERER
     SkXfermode::Mode skiaMode;
     if (!SkXfermode::IsMode(mode, &skiaMode)) {
+        // TODO: Support other modes
         skiaMode = SkXfermode::kSrcOver_Mode;
     }
     return new SkiaComposeShader(shaderA, shaderB, skiaMode, shader);
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index 716d960..147e1fa 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -221,6 +221,18 @@
     }
 }
 
+bool TextLayout::prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
+        jsize contextCount, jchar* shaped) {
+    UErrorCode status = U_ZERO_ERROR;
+    count = shapeRtlText(context, start, count, contextCount, shaped, status);
+    if (U_SUCCESS(status)) {
+        return true;
+    } else {
+        LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
+    }
+    return false;
+}
+
 void TextLayout::drawTextRun(SkPaint* paint, const jchar* chars,
                              jint start, jint count, jint contextCount,
                              int dirFlags, jfloat x, jfloat y, SkCanvas* canvas) {
@@ -231,12 +243,8 @@
      uint8_t rtl = dirFlags & 0x1;
      if (rtl) {
          SkAutoSTMalloc<80, jchar> buffer(contextCount);
-         UErrorCode status = U_ZERO_ERROR;
-         count = shapeRtlText(chars, start, count, contextCount, buffer.get(), status);
-         if (U_SUCCESS(status)) {
+         if (prepareRtlTextRun(chars, start, count, contextCount, buffer.get())) {
              canvas->drawText(buffer.get(), count << 1, x_, y_, *paint);
-         } else {
-             LOG(LOG_WARN, "LAYOUT", "drawTextRun error %d\n", status);
          }
      } else {
          canvas->drawText(chars + start, count << 1, x_, y_, *paint);
diff --git a/core/jni/android/graphics/TextLayout.h b/core/jni/android/graphics/TextLayout.h
index 3d05e18..8f666c0 100644
--- a/core/jni/android/graphics/TextLayout.h
+++ b/core/jni/android/graphics/TextLayout.h
@@ -66,6 +66,9 @@
                                
    static bool prepareText(SkPaint *paint, const jchar* text, jsize len, jint bidiFlags,
         const jchar** outText, int32_t* outBytes, jchar** outBuffer);
+    static bool prepareRtlTextRun(const jchar* context, jsize start, jsize& count,
+        jsize contextCount, jchar* shaped);
+        
 
 private:
     static bool needsLayout(const jchar* text, jint len, jint bidiFlags);
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 1feb3b3..0932473a 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -600,7 +600,7 @@
 static jint
 loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQueue,
         jstring internalDataDir, jstring externalDataDir, int sdkVersion,
-        jobject jAssetMgr)
+        jobject jAssetMgr, jbyteArray savedState)
 {
     LOG_TRACE("loadNativeCode_native");
 
@@ -666,7 +666,18 @@
         
         code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
 
-        code->createActivityFunc(code, NULL, 0);
+        jbyte* rawSavedState = NULL;
+        jsize rawSavedSize = 0;
+        if (savedState != NULL) {
+            rawSavedState = env->GetByteArrayElements(savedState, NULL);
+            rawSavedSize = env->GetArrayLength(savedState);
+        }
+
+        code->createActivityFunc(code, rawSavedState, rawSavedSize);
+
+        if (rawSavedState != NULL) {
+            env->ReleaseByteArrayElements(savedState, rawSavedState, 0);
+        }
     }
     
     return (jint)code;
@@ -706,17 +717,31 @@
     }
 }
 
-static void
+static jbyteArray
 onSaveInstanceState_native(JNIEnv* env, jobject clazz, jint handle)
 {
     LOG_TRACE("onSaveInstanceState_native");
+
+    jbyteArray array = NULL;
+
     if (handle != 0) {
         NativeCode* code = (NativeCode*)handle;
         if (code->callbacks.onSaveInstanceState != NULL) {
             size_t len = 0;
-            code->callbacks.onSaveInstanceState(code, &len);
+            jbyte* state = (jbyte*)code->callbacks.onSaveInstanceState(code, &len);
+            if (len > 0) {
+                array = env->NewByteArray(len);
+                if (array != NULL) {
+                    env->SetByteArrayRegion(array, 0, len, state);
+                }
+            }
+            if (state != NULL) {
+                free(state);
+            }
         }
     }
+
+    return array;
 }
 
 static void
@@ -744,6 +769,18 @@
 }
 
 static void
+onConfigurationChanged_native(JNIEnv* env, jobject clazz, jint handle)
+{
+    LOG_TRACE("onConfigurationChanged_native");
+    if (handle != 0) {
+        NativeCode* code = (NativeCode*)handle;
+        if (code->callbacks.onConfigurationChanged != NULL) {
+            code->callbacks.onConfigurationChanged(code);
+        }
+    }
+}
+
+static void
 onLowMemory_native(JNIEnv* env, jobject clazz, jint handle)
 {
     LOG_TRACE("onLowMemory_native");
@@ -934,14 +971,15 @@
 }
 
 static const JNINativeMethod g_methods[] = {
-    { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;)I",
+    { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[B)I",
             (void*)loadNativeCode_native },
     { "unloadNativeCode", "(I)V", (void*)unloadNativeCode_native },
     { "onStartNative", "(I)V", (void*)onStart_native },
     { "onResumeNative", "(I)V", (void*)onResume_native },
-    { "onSaveInstanceStateNative", "(I)V", (void*)onSaveInstanceState_native },
+    { "onSaveInstanceStateNative", "(I)[B", (void*)onSaveInstanceState_native },
     { "onPauseNative", "(I)V", (void*)onPause_native },
     { "onStopNative", "(I)V", (void*)onStop_native },
+    { "onConfigurationChangedNative", "(I)V", (void*)onConfigurationChanged_native },
     { "onLowMemoryNative", "(I)V", (void*)onLowMemory_native },
     { "onWindowFocusChangedNative", "(IZ)V", (void*)onWindowFocusChanged_native },
     { "onSurfaceCreatedNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceCreated_native },
diff --git a/core/jni/android_content_res_Configuration.cpp b/core/jni/android_content_res_Configuration.cpp
new file mode 100644
index 0000000..28a43ab
--- /dev/null
+++ b/core/jni/android_content_res_Configuration.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Configuration"
+
+#include <utils/Log.h>
+#include "utils/misc.h"
+
+#include "jni.h"
+#include <android_runtime/android_content_res_Configuration.h>
+#include "android_runtime/AndroidRuntime.h"
+
+namespace android {
+
+static struct {
+    jclass clazz;
+
+    jfieldID mcc;
+    jfieldID mnc;
+    jfieldID locale;
+    jfieldID screenLayout;
+    jfieldID touchscreen;
+    jfieldID keyboard;
+    jfieldID keyboardHidden;
+    jfieldID hardKeyboardHidden;
+    jfieldID navigation;
+    jfieldID navigationHidden;
+    jfieldID orientation;
+    jfieldID uiMode;
+} gConfigurationClassInfo;
+
+void android_Configuration_getFromJava(
+        JNIEnv* env, jobject clazz, struct AConfiguration* out) {
+    out->mcc = env->GetIntField(clazz, gConfigurationClassInfo.mcc);
+    out->mnc = env->GetIntField(clazz, gConfigurationClassInfo.mnc);
+    out->screenLayout = env->GetIntField(clazz, gConfigurationClassInfo.screenLayout);
+    out->touchscreen = env->GetIntField(clazz, gConfigurationClassInfo.touchscreen);
+    out->keyboard = env->GetIntField(clazz, gConfigurationClassInfo.keyboard);
+    out->navigation = env->GetIntField(clazz, gConfigurationClassInfo.navigation);
+
+    out->inputFlags = env->GetIntField(clazz, gConfigurationClassInfo.keyboardHidden);
+    int hardKeyboardHidden = env->GetIntField(clazz, gConfigurationClassInfo.hardKeyboardHidden);
+    if (out->inputFlags == ACONFIGURATION_KEYSHIDDEN_NO
+            && hardKeyboardHidden == 2) {
+        out->inputFlags = ACONFIGURATION_KEYSHIDDEN_SOFT;
+    }
+    out->inputFlags |= env->GetIntField(clazz, gConfigurationClassInfo.navigationHidden)
+            << ResTable_config::SHIFT_NAVHIDDEN;
+
+    out->orientation = env->GetIntField(clazz, gConfigurationClassInfo.orientation);
+    out->uiMode = env->GetIntField(clazz, gConfigurationClassInfo.uiMode);
+}
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    //{ "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)Z",
+    //        (void*) android_content_res_ObbScanner_getObbInfo },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_content_res_Configuration(JNIEnv* env)
+{
+    FIND_CLASS(gConfigurationClassInfo.clazz, "android/content/res/Configuration");
+
+    GET_FIELD_ID(gConfigurationClassInfo.mcc, gConfigurationClassInfo.clazz,
+            "mcc", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.mnc, gConfigurationClassInfo.clazz,
+            "mnc", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.locale, gConfigurationClassInfo.clazz,
+            "locale", "Ljava/util/Locale;");
+    GET_FIELD_ID(gConfigurationClassInfo.screenLayout, gConfigurationClassInfo.clazz,
+            "screenLayout", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.touchscreen, gConfigurationClassInfo.clazz,
+            "touchscreen", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.keyboard, gConfigurationClassInfo.clazz,
+            "keyboard", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.keyboardHidden, gConfigurationClassInfo.clazz,
+            "keyboardHidden", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.hardKeyboardHidden, gConfigurationClassInfo.clazz,
+            "hardKeyboardHidden", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz,
+            "navigation", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.navigationHidden, gConfigurationClassInfo.clazz,
+            "navigationHidden", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.orientation, gConfigurationClassInfo.clazz,
+            "orientation", "I");
+    GET_FIELD_ID(gConfigurationClassInfo.uiMode, gConfigurationClassInfo.clazz,
+            "uiMode", "I");
+
+    return AndroidRuntime::registerNativeMethods(env, "android/content/res/Configuration", gMethods,
+            NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index bb1a9e3..9f94af9 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -27,6 +27,7 @@
 #include <SkPaint.h>
 #include <SkRegion.h>
 #include <SkScalerContext.h>
+#include <SkTemplates.h>
 #include <SkXfermode.h>
 
 #include <OpenGLRenderer.h>
@@ -235,6 +236,16 @@
     renderer->drawRect(left, top, right, bottom, paint);
 }
 
+static void android_view_GLES20Canvas_drawRects(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkRegion* region, SkPaint* paint) {
+    SkRegion::Iterator it(*region);
+    while (!it.done()) {
+        const SkIRect& r = it.rect();
+        renderer->drawRect(r.fLeft, r.fTop, r.fRight, r.fBottom, paint);
+        it.next();
+    }
+}
+
 static void android_view_GLES20Canvas_drawPath(JNIEnv* env, jobject canvas,
         OpenGLRenderer* renderer, SkPath* path, SkPaint* paint) {
     renderer->drawPath(path, paint);
@@ -275,6 +286,23 @@
     }
 }
 
+static void renderTextRun(OpenGLRenderer* renderer, const jchar* text,
+        jint start, jint count, jint contextCount, jfloat x, jfloat y,
+        int flags, SkPaint* paint) {
+    uint8_t rtl = flags & 0x1;
+    if (rtl) {
+        SkAutoSTMalloc<80, jchar> buffer(contextCount);
+        jchar* shaped = buffer.get();
+        if (TextLayout::prepareRtlTextRun(text, start, count, contextCount, shaped)) {
+            renderer->drawText((const char*) shaped, count << 1, count, x, y, paint);
+        } else {
+            LOGW("drawTextRun error");
+        }
+    } else {
+        renderer->drawText((const char*) (text + start), count << 1, count, x, y, paint);
+    }
+}
+
 static void android_view_GLES20Canvas_drawTextArray(JNIEnv* env, jobject canvas,
         OpenGLRenderer* renderer, jcharArray text, int index, int count,
         jfloat x, jfloat y, int flags, SkPaint* paint) {
@@ -291,6 +319,28 @@
     env->ReleaseStringChars(text, textArray);
 }
 
+static void android_view_GLES20Canvas_drawTextRunArray(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jcharArray text, int index, int count,
+        int contextIndex, int contextCount, jfloat x, jfloat y, int dirFlags,
+        SkPaint* paint) {
+    jchar* textArray = env->GetCharArrayElements(text, NULL);
+    renderTextRun(renderer, textArray + contextIndex, index - contextIndex,
+            count, contextCount, x, y, dirFlags, paint);
+    env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
+ }
+
+static void android_view_GLES20Canvas_drawTextRun(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jstring text, int start, int end,
+        int contextStart, int contextEnd, jfloat x, jfloat y, int dirFlags,
+        SkPaint* paint) {
+    const jchar* textArray = env->GetStringChars(text, NULL);
+    jint count = end - start;
+    jint contextCount = contextEnd - contextStart;
+    renderTextRun(renderer, textArray + contextStart, start - contextStart,
+            count, contextCount, x, y, dirFlags, paint);
+    env->ReleaseStringChars(text, textArray);
+}
+
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -312,50 +362,55 @@
 const char* const kClassPathName = "android/view/GLES20Canvas";
 
 static JNINativeMethod gMethods[] = {
-    {   "nIsAvailable",       "()Z",             (void*) android_view_GLES20Canvas_isAvailable },
+    { "nIsAvailable",       "()Z",             (void*) android_view_GLES20Canvas_isAvailable },
+
 #ifdef USE_OPENGL_RENDERER
+    { "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
+    { "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
+    { "nSetViewport",       "(III)V",          (void*) android_view_GLES20Canvas_setViewport },
+    { "nPrepare",           "(I)V",            (void*) android_view_GLES20Canvas_prepare },
 
-    {   "nCreateRenderer",    "()I",             (void*) android_view_GLES20Canvas_createRenderer },
-    {   "nDestroyRenderer",   "(I)V",            (void*) android_view_GLES20Canvas_destroyRenderer },
-    {   "nSetViewport",       "(III)V",          (void*) android_view_GLES20Canvas_setViewport },
-    {   "nPrepare",           "(I)V",            (void*) android_view_GLES20Canvas_prepare },
+    { "nSave",              "(II)I",           (void*) android_view_GLES20Canvas_save },
+    { "nRestore",           "(I)V",            (void*) android_view_GLES20Canvas_restore },
+    { "nRestoreToCount",    "(II)V",           (void*) android_view_GLES20Canvas_restoreToCount },
+    { "nGetSaveCount",      "(I)I",            (void*) android_view_GLES20Canvas_getSaveCount },
 
-    {   "nSave",              "(II)I",           (void*) android_view_GLES20Canvas_save },
-    {   "nRestore",           "(I)V",            (void*) android_view_GLES20Canvas_restore },
-    {   "nRestoreToCount",    "(II)V",           (void*) android_view_GLES20Canvas_restoreToCount },
-    {   "nGetSaveCount",      "(I)I",            (void*) android_view_GLES20Canvas_getSaveCount },
+    { "nSaveLayer",         "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayer },
+    { "nSaveLayerAlpha",    "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayerAlpha },
 
-    {   "nSaveLayer",         "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayer },
-    {   "nSaveLayerAlpha",    "(IFFFFII)I",      (void*) android_view_GLES20Canvas_saveLayerAlpha },
+    { "nQuickReject",       "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_quickReject },
+    { "nClipRect",          "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_clipRectF },
+    { "nClipRect",          "(IIIIII)Z",       (void*) android_view_GLES20Canvas_clipRect },
 
-    {   "nQuickReject",       "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_quickReject },
-    {   "nClipRect",          "(IFFFFI)Z",       (void*) android_view_GLES20Canvas_clipRectF },
-    {   "nClipRect",          "(IIIIII)Z",       (void*) android_view_GLES20Canvas_clipRect },
+    { "nTranslate",         "(IFF)V",          (void*) android_view_GLES20Canvas_translate },
+    { "nRotate",            "(IF)V",           (void*) android_view_GLES20Canvas_rotate },
+    { "nScale",             "(IFF)V",          (void*) android_view_GLES20Canvas_scale },
 
-    {   "nTranslate",         "(IFF)V",          (void*) android_view_GLES20Canvas_translate },
-    {   "nRotate",            "(IF)V",           (void*) android_view_GLES20Canvas_rotate },
-    {   "nScale",             "(IFF)V",          (void*) android_view_GLES20Canvas_scale },
+    { "nSetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_setMatrix },
+    { "nGetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_getMatrix },
+    { "nConcatMatrix",      "(II)V",           (void*) android_view_GLES20Canvas_concatMatrix },
 
-    {   "nSetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_setMatrix },
-    {   "nGetMatrix",         "(II)V",           (void*) android_view_GLES20Canvas_getMatrix },
-    {   "nConcatMatrix",      "(II)V",           (void*) android_view_GLES20Canvas_concatMatrix },
+    { "nDrawBitmap",        "(IIFFI)V",        (void*) android_view_GLES20Canvas_drawBitmap },
+    { "nDrawBitmap",        "(IIFFFFFFFFI)V",  (void*) android_view_GLES20Canvas_drawBitmapRect },
+    { "nDrawBitmap",        "(IIII)V",         (void*) android_view_GLES20Canvas_drawBitmapMatrix },
+    { "nDrawPatch",         "(II[BFFFFI)V",    (void*) android_view_GLES20Canvas_drawPatch },
+    { "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
+    { "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
+    { "nDrawRects",         "(III)V",          (void*) android_view_GLES20Canvas_drawRects },
+    { "nDrawPath",          "(III)V",          (void*) android_view_GLES20Canvas_drawPath },
 
-    {   "nDrawBitmap",        "(IIFFI)V",        (void*) android_view_GLES20Canvas_drawBitmap },
-    {   "nDrawBitmap",        "(IIFFFFFFFFI)V",  (void*) android_view_GLES20Canvas_drawBitmapRect },
-    {   "nDrawBitmap",        "(IIII)V",         (void*) android_view_GLES20Canvas_drawBitmapMatrix },
-    {   "nDrawPatch",         "(II[BFFFFI)V",    (void*) android_view_GLES20Canvas_drawPatch },
-    {   "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
-    {   "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
-    {   "nDrawPath",          "(III)V",          (void*) android_view_GLES20Canvas_drawPath },
+    { "nResetModifiers",    "(I)V",            (void*) android_view_GLES20Canvas_resetModifiers },
+    { "nSetupShader",       "(II)V",           (void*) android_view_GLES20Canvas_setupShader },
+    { "nSetupColorFilter",  "(II)V",           (void*) android_view_GLES20Canvas_setupColorFilter },
 
-    {   "nResetModifiers",    "(I)V",            (void*) android_view_GLES20Canvas_resetModifiers },
-    {   "nSetupShader",       "(II)V",           (void*) android_view_GLES20Canvas_setupShader },
-    {   "nSetupColorFilter",  "(II)V",           (void*) android_view_GLES20Canvas_setupColorFilter },
-
-    {   "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
-    {   "nDrawText",          "(ILjava/lang/String;IIFFII)V",
+    { "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
+    { "nDrawText",          "(ILjava/lang/String;IIFFII)V",
             (void*) android_view_GLES20Canvas_drawText },
 
+    { "nDrawTextRun",       "(I[CIIIIFFII)V",  (void*) android_view_GLES20Canvas_drawTextRunArray },
+    { "nDrawTextRun",       "(ILjava/lang/String;IIIIFFII)V",
+            (void*) android_view_GLES20Canvas_drawTextRun },
+
     {   "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
             (void*) android_view_GLES20Canvas_getClipBounds },
 #endif
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 01ded68..1f66d05 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1284,6 +1284,9 @@
                 android:finishOnCloseSystemDialogs="true"
                 android:excludeFromRecents="true">
         </activity>
+        <activity android:name="com.android.internal.app.PlatLogoActivity"
+                android:theme="@style/Theme.NoTitleBar.Fullscreen">
+        </activity>
         <activity android:name="com.android.internal.app.DisableCarModeActivity"
                 android:theme="@style/Theme.NoDisplay"
                 android:excludeFromRecents="true">
diff --git a/core/res/res/drawable-nodpi/platlogo.jpg b/core/res/res/drawable-nodpi/platlogo.jpg
new file mode 100644
index 0000000..0e7780c
--- /dev/null
+++ b/core/res/res/drawable-nodpi/platlogo.jpg
Binary files differ
diff --git a/core/res/res/layout/action_mode_bar.xml b/core/res/res/layout/action_mode_bar.xml
index 8e2e69d..acf327e 100644
--- a/core/res/res/layout/action_mode_bar.xml
+++ b/core/res/res/layout/action_mode_bar.xml
@@ -19,4 +19,5 @@
 <com.android.internal.widget.ActionBarContextView
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
-     android:layout_height="wrap_content" />
+     android:layout_height="wrap_content"
+     android:visibility="gone" />
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index f7f4d69..e768308 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -746,8 +746,8 @@
     <string name="force_close" msgid="3653416315450806396">"Ukončit aplikaci"</string>
     <string name="report" msgid="4060218260984795706">"Nahlásit"</string>
     <string name="wait" msgid="7147118217226317732">"Počkat"</string>
-    <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces<xliff:g id="PROCESS">%2$s</xliff:g>) porušila své samovynucované zásady StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své samovynucované zásady StrictMode."</string>
+    <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) porušila své vlastní vynucené zásady StrictMode."</string>
+    <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své vlastní vynucené zásady StrictMode."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
     <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Tuto možnost vyberte, chcete-li přepnout na aplikaci."</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Přepnout mezi aplikacemi?"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ac42fd1..87376fe 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -749,7 +749,7 @@
     <string name="smv_application" msgid="295583804361236288">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) hat gegen ihre selbsterzwungene StrictMode-Richtlinie verstoßen."</string>
     <string name="smv_process" msgid="5120397012047462446">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> hat gegen seine selbsterzwungene StrictMode-Richtlinie verstoßen."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswahl zum Wechseln in die Anwendung"</string>
+    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswählen zum Wechseln in die Anwendung"</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Anwendung wechseln?"</string>
     <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Eine andere Anwendung wird bereits ausgeführt und muss vor dem Start einer neuen Anwendung beendet werden."</string>
     <string name="old_app_action" msgid="493129172238566282">"Zu <xliff:g id="OLD_APP">%1$s</xliff:g> zurückkehren"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index c2dd90b..04b6add 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -746,8 +746,8 @@
     <string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string>
     <string name="report" msgid="4060218260984795706">"Rapport"</string>
     <string name="wait" msgid="7147118217226317732">"Attendre"</string>
-    <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles StrictMode."</string>
-    <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles StrictMode."</string>
+    <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles du mode strict."</string>
+    <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles du mode strict."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string>
     <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Sélectionner pour changer d\'application"</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Passer d\'une application à l\'autre ?"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index fcb7eb7..21e8ad8 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -173,7 +173,7 @@
     <string name="permlab_statusBar" msgid="7417192629601890791">"отключать или изменять строку состояния"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"Позволяет приложению отключать строку состояния или добавлять/удалять системные значки."</string>
     <string name="permlab_statusBarService" msgid="7247281911387931485">"строка состояния"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению быть строкой состояния."</string>
+    <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению заменять строку состояния."</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"разворачивать/сворачивать строку состояния"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Позволяет приложению разворачивать или сворачивать строку состояния."</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"перехватывать исходящие вызовы"</string>
@@ -315,7 +315,7 @@
     <string name="permlab_recordAudio" msgid="3876049771427466323">"записывать аудио"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"Позволяет приложению получать доступ к пути аудиозаписи."</string>
     <string name="permlab_camera" msgid="3616391919559751192">"снимать фото и видео"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры. Это дает приложению возможность в любое время получать изображения с объектива камеры."</string>
+    <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры в любое время."</string>
     <string name="permlab_brick" msgid="8337817093326370537">"отключать телефон"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"Позволяет данному приложению отключить телефон навсегда. Это очень опасно."</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"принудительно перезагружать телефон"</string>
@@ -749,7 +749,7 @@
     <string name="smv_application" msgid="295583804361236288">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) нарушило собственную политику StrictMode."</string>
     <string name="smv_process" msgid="5120397012047462446">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> нарушил собственную политику StrictMode."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы переключиться в приложение"</string>
+    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы перейти к приложению"</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Переключить приложения?"</string>
     <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Выполняется другое приложение, которое должно быть остановлено прежде, чем запускать новое."</string>
     <string name="old_app_action" msgid="493129172238566282">"Вернуться к приложению <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 8e623e8..e8f5a20 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -563,7 +563,7 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM-kortet är låst."</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"Låser upp SIM-kort…"</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
-    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har angett ditt lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"Du har angett fel lösenord <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"Du har angett din PIN-kod fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. "\n\n"Försök igen om <xliff:g id="NUMBER_1">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"Du har ritat ditt grafiska lösenord fel <xliff:g id="NUMBER_0">%d</xliff:g> gånger. Efter <xliff:g id="NUMBER_1">%d</xliff:g> försök till kommer du att uppmanas att låsa upp telefonen med din Google-inloggning."\n\n" Försök igen om <xliff:g id="NUMBER_2">%d</xliff:g> sekunder."</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"Försök igen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b207db9..a9dd887 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -746,8 +746,8 @@
     <string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string>
     <string name="report" msgid="4060218260984795706">"Rapor"</string>
     <string name="wait" msgid="7147118217226317732">"Bekle"</string>
-    <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string>
-    <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden zorunlu StrictMode politikasını ihlal etti."</string>
+    <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string>
+    <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string>
     <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Uygulama değiştirmeyi seçin"</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Uygulamaların arasında geçiş yapılsın mı?"</string>
diff --git a/core/res/res/values-xlarge/styles.xml b/core/res/res/values-xlarge/styles.xml
index 40e423e..ff7df7c 100644
--- a/core/res/res/values-xlarge/styles.xml
+++ b/core/res/res/values-xlarge/styles.xml
@@ -19,7 +19,6 @@
 
     <style name="TextAppearance.StatusBar">
         <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
-        <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
     </style>
     <style name="TextAppearance.StatusBar.Ticker">
     </style>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 4d78b86..0949553 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -315,7 +315,7 @@
     <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"允许应用程序访问录音路径。"</string>
     <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中看到的图片。"</string>
+    <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中的图片。"</string>
     <string name="permlab_brick" msgid="8337817093326370537">"永久停用手机"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"允许应用程序永久停用整个手机,这非常危险。"</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"强行重新启动手机"</string>
@@ -747,7 +747,7 @@
     <string name="report" msgid="4060218260984795706">"报告"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
     <string name="smv_application" msgid="295583804361236288">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
-    <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策"</string>
+    <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策。"</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string>
     <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"选择以切换到该应用程序"</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"要切换应用程序吗?"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 4ab9907..2f0a49b 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -34,7 +34,7 @@
     <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"語音留言"</string>
     <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string>
     <string name="mmiError" msgid="5154499457739052907">"連線發生問題或錯誤的 MMI 碼。"</string>
-    <string name="mmiFdnError" msgid="5224398216385316471">"僅允許在固定撥號時使用此操作。"</string>
+    <string name="mmiFdnError" msgid="5224398216385316471">"僅限對固定撥號號碼執行此作業。"</string>
     <string name="serviceEnabled" msgid="8147278346414714315">"服務已啟用。"</string>
     <string name="serviceEnabledFor" msgid="6856228140453471041">"已啟用服務:"</string>
     <string name="serviceDisabled" msgid="1937553226592516411">"服務已停用。"</string>
@@ -173,7 +173,7 @@
     <string name="permlab_statusBar" msgid="7417192629601890791">"停用或變更狀態列"</string>
     <string name="permdesc_statusBar" msgid="1365473595331989732">"允許應用程式停用狀態列或新增、移除系統圖示。"</string>
     <string name="permlab_statusBarService" msgid="7247281911387931485">"狀態列"</string>
-    <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式成為狀態列。"</string>
+    <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式以狀態列顯示。"</string>
     <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展開/收攏狀態列"</string>
     <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允許應用程式展開或收攏狀態列。"</string>
     <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"攔截撥出電話"</string>
@@ -314,8 +314,8 @@
     <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允許應用程式編輯全域音訊設定,例如音量與路由。"</string>
     <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string>
     <string name="permdesc_recordAudio" msgid="6493228261176552356">"允許應用程式存取音訊錄製路徑。"</string>
-    <string name="permlab_camera" msgid="3616391919559751192">"拍照和錄影"</string>
-    <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影。此功能可讓應用程式隨時透過相機收集圖片。"</string>
+    <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string>
+    <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影,此功能可讓應用程式隨時透過相機收集圖片。"</string>
     <string name="permlab_brick" msgid="8337817093326370537">"永久停用電話"</string>
     <string name="permdesc_brick" msgid="5569526552607599221">"允許應用程式永久停用手機。此項操作非常危險。"</string>
     <string name="permlab_reboot" msgid="2898560872462638242">"強制重開機"</string>
@@ -535,7 +535,7 @@
     <string name="orgTypeCustom" msgid="225523415372088322">"自訂"</string>
     <string name="keyguard_password_enter_pin_code" msgid="3731488827218876115">"輸入 PIN 碼"</string>
     <string name="keyguard_password_enter_password_code" msgid="9138158344813213754">"輸入密碼即可解鎖"</string>
-    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"輸入 PIN 即可解鎖"</string>
+    <string name="keyguard_password_enter_pin_password_code" msgid="638347075625491514">"輸入 PIN 進行解鎖"</string>
     <string name="keyguard_password_wrong_pin_code" msgid="1295984114338107718">"PIN 碼錯誤!"</string>
     <string name="keyguard_label_text" msgid="861796461028298424">"如要解鎖,請按 Menu 鍵,然後按 0。"</string>
     <string name="emergency_call_dialog_number_for_display" msgid="696192103195090970">"緊急電話號碼"</string>
@@ -563,8 +563,8 @@
     <string name="lockscreen_sim_locked_message" msgid="8066660129206001039">"SIM 卡已鎖定。"</string>
     <string name="lockscreen_sim_unlock_progress_dialog_message" msgid="595323214052881264">"解鎖 SIM 卡中..."</string>
     <string name="lockscreen_too_many_failed_attempts_dialog_message" msgid="3514742106066877476">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。"\n\n" 請在 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再嘗試。"</string>
-    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已輸入 <xliff:g id="NUMBER_0">%d</xliff:g> 次不正確的密碼。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string>
-    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已輸入 <xliff:g id="NUMBER_0">%d</xliff:g> 次不正確的 PIN。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒內再試一次。"</string>
+    <string name="lockscreen_too_many_failed_password_attempts_dialog_message" msgid="4906034376425175381">"您已 <xliff:g id="NUMBER_0">%d</xliff:g> 次輸入不正確的密碼。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string>
+    <string name="lockscreen_too_many_failed_pin_attempts_dialog_message" msgid="6827749231465145590">"您已 <xliff:g id="NUMBER_0">%d</xliff:g> 次輸入不正確的 PIN。"\n\n"請於 <xliff:g id="NUMBER_1">%d</xliff:g> 秒後再試一次。"</string>
     <string name="lockscreen_failed_attempts_almost_glogin" msgid="3351013842320127827">"畫出解鎖圖形已錯誤 <xliff:g id="NUMBER_0">%d</xliff:g> 次。再錯誤 <xliff:g id="NUMBER_1">%d</xliff:g> 次後,系統會要求使用 Google 登入來解鎖。"\n\n" 請在 <xliff:g id="NUMBER_2">%d</xliff:g> 秒後再試一次。"</string>
     <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6251480343394389665">"<xliff:g id="NUMBER">%d</xliff:g> 秒後再試一次。"</string>
     <string name="lockscreen_forgot_pattern_button_text" msgid="2626999449610695930">"忘記解鎖圖形?"</string>
@@ -746,12 +746,12 @@
     <string name="force_close" msgid="3653416315450806396">"強制關閉"</string>
     <string name="report" msgid="4060218260984795706">"回報"</string>
     <string name="wait" msgid="7147118217226317732">"等待"</string>
-    <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行實施的 StrictMode 政策。"</string>
-    <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行實施的 StrictMode 政策。"</string>
+    <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string>
+    <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string>
     <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string>
-    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取切換應用程式"</string>
+    <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取以切換到該應用程式"</string>
     <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"切換應用程式?"</string>
-    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行,才能啟動新的應用程式。"</string>
+    <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行該應用程式,才能啟動新的應用程式。"</string>
     <string name="old_app_action" msgid="493129172238566282">"返回 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
     <string name="old_app_description" msgid="942967900237208466">"請勿啟動新的應用程式。"</string>
     <string name="new_app_action" msgid="5472756926945440706">"啟動 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string>
@@ -862,15 +862,15 @@
     <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"採用預先共用金鑰的 L2TP/IPSec VPN"</string>
     <string name="l2tp_ipsec_crt_vpn_description" msgid="5382714073103653577">"採用憑證的 L2TP/IPSec VPN"</string>
     <string name="upload_file" msgid="2897957172366730416">"選擇檔案"</string>
-    <string name="no_file_chosen" msgid="6363648562170759465">"沒有選擇檔案"</string>
+    <string name="no_file_chosen" msgid="6363648562170759465">"未選擇任何檔案"</string>
     <string name="reset" msgid="2448168080964209908">"重設"</string>
     <string name="submit" msgid="1602335572089911941">"提交"</string>
     <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"已啟用車用模式"</string>
     <string name="car_mode_disable_notification_message" msgid="668663626721675614">"選取結束車用模式。"</string>
     <string name="tethered_notification_title" msgid="3146694234398202601">"數據連線或無線基地台已啟用"</string>
     <string name="tethered_notification_message" msgid="3067108323903048927">"輕觸以設定"</string>
-    <string name="back_button_label" msgid="2300470004503343439">"上一個"</string>
-    <string name="next_button_label" msgid="1080555104677992408">"下一個"</string>
+    <string name="back_button_label" msgid="2300470004503343439">"上一頁"</string>
+    <string name="next_button_label" msgid="1080555104677992408">"下一頁"</string>
     <string name="throttle_warning_notification_title" msgid="4890894267454867276">"高行動資料用量"</string>
     <string name="throttle_warning_notification_message" msgid="2609734763845705708">"輕觸即可瞭解更多有關行動資料用量的詳細資訊"</string>
     <string name="throttled_notification_title" msgid="6269541897729781332">"已達行動資料上限"</string>
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
index 0fe83e1..cbd8714 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
@@ -17,13 +17,11 @@
 package android.bluetooth;
 
 import android.app.Instrumentation;
-import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
 public class BluetoothStressTest extends InstrumentationTestCase {
@@ -161,7 +159,6 @@
         mContext.unregisterReceiver(mReceiver);
     }
 
-    @LargeTest
     public void testEnableDisable() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
 
@@ -172,7 +169,6 @@
         }
     }
 
-    @LargeTest
     public void testDiscoverable() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         enable(adapter);
@@ -186,7 +182,6 @@
         disable(adapter);
     }
 
-    @LargeTest
     public void testScan() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         enable(adapter);
@@ -336,7 +331,7 @@
         mReceiver.resetFiredFlags();
 
         if (!adapter.isEnabled()) {
-            fail("undiscoverable(): bluetooth not enabled");
+            fail("undiscoverable() bluetooth not enabled");
         }
 
         int scanMode = adapter.getScanMode();
@@ -374,7 +369,7 @@
         mReceiver.resetFiredFlags();
 
         if (!adapter.isEnabled()) {
-            fail("startScan(): bluetooth not enabled");
+            fail("startScan() bluetooth not enabled");
         }
 
         if (adapter.isDiscovering()) {
@@ -404,7 +399,7 @@
         mReceiver.resetFiredFlags();
 
         if (!adapter.isEnabled()) {
-            fail("stopScan(): bluetooth not enabled");
+            fail("stopScan() bluetooth not enabled");
         }
 
         if (!adapter.isDiscovering()) {
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index ac1f277..e88211a 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -20,7 +20,12 @@
     an {@link android.graphics.Xfermode} subclass.
 */
 public class ComposeShader extends Shader {
+    /**
+     * Hold onto the shaders to avoid GC.
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
     private final Shader mShaderA;
+    @SuppressWarnings({"UnusedDeclaration"})
     private final Shader mShaderB;
 
     /** Create a new compose shader, given shaders A, B, and a combining mode.
@@ -36,8 +41,14 @@
         mShaderB = shaderB;
         native_instance = nativeCreate1(shaderA.native_instance, shaderB.native_instance,
                 (mode != null) ? mode.native_instance : 0);
-        native_shader = nativePostCreate1(native_instance, shaderA.native_shader,
-                shaderB.native_shader, (mode != null) ? mode.native_instance : 0);
+        if (mode instanceof PorterDuffXfermode) {
+            PorterDuff.Mode pdMode = ((PorterDuffXfermode) mode).mode;
+            native_shader = nativePostCreate1(native_instance, shaderA.native_shader,
+                    shaderB.native_shader, pdMode != null ? pdMode.nativeInt : 0);
+        } else {
+            native_shader = nativePostCreate1(native_instance, shaderA.native_shader,
+                    shaderB.native_shader, mode != null ? mode.native_instance : 0);
+        }
     }
 
     /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index cb2c6a2..c3416a0 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -16,6 +16,8 @@
 
 package android.graphics;
 
+import android.view.HardwareRenderer;
+
 /**
  * The Path class encapsulates compound (multiple contour) geometric paths
  * consisting of straight line segments, quadratic curves, and cubic curves.
@@ -24,12 +26,27 @@
  * text on a path.
  */
 public class Path {
+    /**
+     * @hide
+     */
+    public final int mNativePath;
+
+    /**
+     * @hide
+     */
+    public boolean isSimplePath = true;
+    /**
+     * @hide
+     */
+    public Region rects;
+    private boolean mDetectSimplePaths;
 
     /**
      * Create an empty path
      */
     public Path() {
         mNativePath = init1();
+        mDetectSimplePaths = HardwareRenderer.isAvailable();
     }
 
     /**
@@ -43,6 +60,7 @@
             valNative = src.mNativePath;
         }
         mNativePath = init2(valNative);
+        mDetectSimplePaths = HardwareRenderer.isAvailable();
     }
     
     /**
@@ -50,6 +68,10 @@
      * This does NOT change the fill-type setting.
      */
     public void reset() {
+        isSimplePath = true;
+        if (mDetectSimplePaths) {
+            if (rects != null) rects.setEmpty();
+        }
         native_reset(mNativePath);
     }
 
@@ -58,6 +80,10 @@
      * keeps the internal data structure for faster reuse.
      */
     public void rewind() {
+        isSimplePath = true;
+        if (mDetectSimplePaths) {
+            if (rects != null) rects.setEmpty();
+        }
         native_rewind(mNativePath);
     }
 
@@ -65,6 +91,7 @@
     */
     public void set(Path src) {
         if (this != src) {
+            isSimplePath = src.isSimplePath;
             native_set(mNativePath, src.mNativePath);
         }
     }
@@ -160,6 +187,7 @@
      * @param bounds Returns the computed bounds of the path's control points.
      * @param exact This parameter is no longer used.
      */
+    @SuppressWarnings({"UnusedDeclaration"})
     public void computeBounds(RectF bounds, boolean exact) {
         native_computeBounds(mNativePath, bounds);
     }
@@ -236,6 +264,7 @@
      * @param y2 The y-coordinate of the end point on a quadratic curve
      */
     public void quadTo(float x1, float y1, float x2, float y2) {
+        isSimplePath = false;
         native_quadTo(mNativePath, x1, y1, x2, y2);
     }
 
@@ -254,6 +283,7 @@
      *            this contour, for the end point of a quadratic curve
      */
     public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+        isSimplePath = false;
         native_rQuadTo(mNativePath, dx1, dy1, dx2, dy2);
     }
 
@@ -271,6 +301,7 @@
      */
     public void cubicTo(float x1, float y1, float x2, float y2,
                         float x3, float y3) {
+        isSimplePath = false;
         native_cubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
     }
 
@@ -281,6 +312,7 @@
      */
     public void rCubicTo(float x1, float y1, float x2, float y2,
                          float x3, float y3) {
+        isSimplePath = false;
         native_rCubicTo(mNativePath, x1, y1, x2, y2, x3, y3);
     }
 
@@ -299,6 +331,7 @@
      */
     public void arcTo(RectF oval, float startAngle, float sweepAngle,
                       boolean forceMoveTo) {
+        isSimplePath = false;
         native_arcTo(mNativePath, oval, startAngle, sweepAngle, forceMoveTo);
     }
     
@@ -314,6 +347,7 @@
      * @param sweepAngle  Sweep angle (in degrees) measured clockwise
      */
     public void arcTo(RectF oval, float startAngle, float sweepAngle) {
+        isSimplePath = false;
         native_arcTo(mNativePath, oval, startAngle, sweepAngle, false);
     }
     
@@ -322,6 +356,7 @@
      * first point of the contour, a line segment is automatically added.
      */
     public void close() {
+        isSimplePath = false;
         native_close(mNativePath);
     }
 
@@ -351,6 +386,11 @@
         if (rect == null) {
             throw new NullPointerException("need rect parameter");
         }
+        if (mDetectSimplePaths) {
+            if (rects == null) rects = new Region();
+            rects.op((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom,
+                    Region.Op.UNION);
+        }
         native_addRect(mNativePath, rect, dir.nativeInt);
     }
 
@@ -363,8 +403,11 @@
      * @param bottom The bottom of a rectangle to add to the path
      * @param dir    The direction to wind the rectangle's contour
      */
-    public void addRect(float left, float top, float right, float bottom,
-                        Direction dir) {
+    public void addRect(float left, float top, float right, float bottom, Direction dir) {
+        if (mDetectSimplePaths) {
+            if (rects == null) rects = new Region();
+            rects.op((int) left, (int) top, (int) right, (int) bottom, Region.Op.UNION);
+        }
         native_addRect(mNativePath, left, top, right, bottom, dir.nativeInt);
     }
 
@@ -378,6 +421,7 @@
         if (oval == null) {
             throw new NullPointerException("need oval parameter");
         }
+        isSimplePath = false;
         native_addOval(mNativePath, oval, dir.nativeInt);
     }
 
@@ -390,6 +434,7 @@
      * @param dir    The direction to wind the circle's contour
      */
     public void addCircle(float x, float y, float radius, Direction dir) {
+        isSimplePath = false;
         native_addCircle(mNativePath, x, y, radius, dir.nativeInt);
     }
 
@@ -404,6 +449,7 @@
         if (oval == null) {
             throw new NullPointerException("need oval parameter");
         }
+        isSimplePath = false;
         native_addArc(mNativePath, oval, startAngle, sweepAngle);
     }
 
@@ -419,6 +465,7 @@
         if (rect == null) {
             throw new NullPointerException("need rect parameter");
         }
+        isSimplePath = false;
         native_addRoundRect(mNativePath, rect, rx, ry, dir.nativeInt);
     }
     
@@ -438,6 +485,7 @@
         if (radii.length < 8) {
             throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
         }
+        isSimplePath = false;
         native_addRoundRect(mNativePath, rect, radii, dir.nativeInt);
     }
     
@@ -448,6 +496,7 @@
      * @param dx  The amount to translate the path in X as it is added
      */
     public void addPath(Path src, float dx, float dy) {
+        isSimplePath = false;
         native_addPath(mNativePath, src.mNativePath, dx, dy);
     }
 
@@ -457,6 +506,7 @@
      * @param src The path that is appended to the current path
      */
     public void addPath(Path src) {
+        isSimplePath = false;
         native_addPath(mNativePath, src.mNativePath);
     }
 
@@ -466,6 +516,7 @@
      * @param src The path to add as a new contour
      */
     public void addPath(Path src, Matrix matrix) {
+        if (!src.isSimplePath) isSimplePath = false;
         native_addPath(mNativePath, src.mNativePath, matrix.native_instance);
     }
 
@@ -502,6 +553,7 @@
      * @param dy The new Y coordinate for the last point
      */
     public void setLastPoint(float dx, float dy) {
+        isSimplePath = false;
         native_setLastPoint(mNativePath, dx, dy);
     }
 
@@ -537,8 +589,8 @@
             super.finalize();
         }
     }
-    
-    /*package*/ final int ni() {
+
+    final int ni() {
         return mNativePath;
     }
 
@@ -592,9 +644,4 @@
                                                 int dst_path);
     private static native void native_transform(int nPath, int matrix);
     private static native void finalizer(int nPath);
-
-    /**
-     * @hide
-     */
-    public final int mNativePath;
 }
diff --git a/graphics/java/android/graphics/PorterDuffXfermode.java b/graphics/java/android/graphics/PorterDuffXfermode.java
index cb127fd..6ba064c 100644
--- a/graphics/java/android/graphics/PorterDuffXfermode.java
+++ b/graphics/java/android/graphics/PorterDuffXfermode.java
@@ -18,11 +18,17 @@
 
 public class PorterDuffXfermode extends Xfermode {
     /**
+     * @hide
+     */
+    public final PorterDuff.Mode mode;
+
+    /**
      * Create an xfermode that uses the specified porter-duff mode.
      *
      * @param mode           The porter-duff mode that is applied
      */
     public PorterDuffXfermode(PorterDuff.Mode mode) {
+        this.mode = mode;
         native_instance = nativeCreateXfermode(mode.nativeInt);
     }
     
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index 489ef83..e540806 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -20,6 +20,10 @@
 import android.os.Parcelable;
 
 public class Region implements Parcelable {
+    /**
+     * @hide
+     */
+    public final int mNativeRegion;
 
     // the native values for these must match up with the enum in SkRegion.h
     public enum Op {
@@ -329,10 +333,14 @@
     }
 
     protected void finalize() throws Throwable {
-        nativeDestructor(mNativeRegion);
+        try {
+            nativeDestructor(mNativeRegion);
+        } finally {
+            super.finalize();
+        }
     }
     
-    /*package*/ Region(int ni) {
+    Region(int ni) {
         if (ni == 0) {
             throw new RuntimeException();
         }
@@ -345,7 +353,7 @@
         this(ni);
     }
 
-    /*package*/ final int ni() {
+    final int ni() {
         return mNativeRegion;
     }
 
@@ -374,6 +382,4 @@
                                                       Parcel p);
 
     private static native boolean nativeEquals(int native_r1, int native_r2);
-
-    private final int mNativeRegion;
 }
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 42c410e..2467bdc 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -31,7 +31,11 @@
 public class Xfermode {
 
     protected void finalize() throws Throwable {
-        finalizer(native_instance);
+        try {
+            finalizer(native_instance);
+        } finally {
+            super.finalize();
+        }
     }
 
     private static native void finalizer(int native_instance);
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 33ecbea..88f6d43 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -313,18 +313,16 @@
             case RECTANGLE:
                 if (st.mRadiusArray != null) {
                     mPath.reset();
-                    mPath.addRoundRect(mRect, st.mRadiusArray,
-                                       Path.Direction.CW);
+                    mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
                     canvas.drawPath(mPath, mFillPaint);
                     if (haveStroke) {
                         canvas.drawPath(mPath, mStrokePaint);
                     }
-                }
-                else {
+                } else if (st.mRadius > 0.0f) {
                     // since the caller is only giving us 1 value, we will force
                     // it to be square if the rect is too small in one dimension
                     // to show it. If we did nothing, Skia would clamp the rad
-                    // independently along each axis, giving us a thin ellips
+                    // independently along each axis, giving us a thin ellipse
                     // if the rect were very wide but not very tall
                     float rad = st.mRadius;
                     float r = Math.min(mRect.width(), mRect.height()) * 0.5f;
@@ -335,6 +333,11 @@
                     if (haveStroke) {
                         canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
                     }
+                } else {
+                    canvas.drawRect(mRect, mFillPaint);
+                    if (haveStroke) {
+                        canvas.drawRect(mRect, mStrokePaint);
+                    }
                 }
                 break;
             case OVAL:
diff --git a/include/android_runtime/android_content_res_Configuration.h b/include/android_runtime/android_content_res_Configuration.h
new file mode 100644
index 0000000..2f5a982
--- /dev/null
+++ b/include/android_runtime/android_content_res_Configuration.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CONTENT_RES_CONFIGURATION_H
+#define _ANDROID_CONTENT_RES_CONFIGURATION_H
+
+#include <utils/ResourceTypes.h>
+#include <android/configuration.h>
+
+#include "jni.h"
+
+struct AConfiguration : android::ResTable_config {
+};
+
+namespace android {
+
+extern void android_Configuration_getFromJava(
+        JNIEnv* env, jobject clazz, struct AConfiguration* out);
+
+} // namespace android
+
+
+#endif // _ANDROID_CONTENT_RES_CONFIGURATION_H
diff --git a/include/media/stagefright/MediaDefs.h b/include/media/stagefright/MediaDefs.h
index 9cddab2..92ce068 100644
--- a/include/media/stagefright/MediaDefs.h
+++ b/include/media/stagefright/MediaDefs.h
@@ -34,6 +34,8 @@
 extern const char *MEDIA_MIMETYPE_AUDIO_AAC;
 extern const char *MEDIA_MIMETYPE_AUDIO_QCELP;
 extern const char *MEDIA_MIMETYPE_AUDIO_VORBIS;
+extern const char *MEDIA_MIMETYPE_AUDIO_G711_ALAW;
+extern const char *MEDIA_MIMETYPE_AUDIO_G711_MLAW;
 extern const char *MEDIA_MIMETYPE_AUDIO_RAW;
 
 extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4;
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index f162231..71c6c51 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -454,6 +454,8 @@
     virtual void reset();
     virtual void process(const RawEvent* rawEvent);
 
+    virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
 private:
     // Amount that trackball needs to move in order to generate a key event.
     static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h
index 97694ff..9e2bf37 100644
--- a/include/utils/AssetManager.h
+++ b/include/utils/AssetManager.h
@@ -129,6 +129,8 @@
      */
     void setConfiguration(const ResTable_config& config, const char* locale = NULL);
 
+    void getConfiguration(ResTable_config* outConfig) const;
+
     typedef Asset::AccessMode AccessMode;       // typing shortcut
 
     /*
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index c7d9ff1..da86da4 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -31,6 +31,8 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <android/configuration.h>
+
 namespace android {
 
 /** ********************************************************************
@@ -822,25 +824,25 @@
     };
     
     enum {
-        ORIENTATION_ANY  = 0x0000,
-        ORIENTATION_PORT = 0x0001,
-        ORIENTATION_LAND = 0x0002,
-        ORIENTATION_SQUARE = 0x0003,
+        ORIENTATION_ANY  = ACONFIGURATION_ORIENTATION_ANY,
+        ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT,
+        ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND,
+        ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE,
     };
     
     enum {
-        TOUCHSCREEN_ANY  = 0x0000,
-        TOUCHSCREEN_NOTOUCH  = 0x0001,
-        TOUCHSCREEN_STYLUS  = 0x0002,
-        TOUCHSCREEN_FINGER  = 0x0003,
+        TOUCHSCREEN_ANY  = ACONFIGURATION_TOUCHSCREEN_ANY,
+        TOUCHSCREEN_NOTOUCH  = ACONFIGURATION_TOUCHSCREEN_NOTOUCH,
+        TOUCHSCREEN_STYLUS  = ACONFIGURATION_TOUCHSCREEN_STYLUS,
+        TOUCHSCREEN_FINGER  = ACONFIGURATION_TOUCHSCREEN_FINGER,
     };
     
     enum {
-        DENSITY_DEFAULT = 0,
-        DENSITY_LOW = 120,
-        DENSITY_MEDIUM = 160,
-        DENSITY_HIGH = 240,
-        DENSITY_NONE = 0xffff
+        DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT,
+        DENSITY_LOW = ACONFIGURATION_DENSITY_LOW,
+        DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM,
+        DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH,
+        DENSITY_NONE = ACONFIGURATION_DENSITY_NONE
     };
     
     union {
@@ -853,33 +855,34 @@
     };
     
     enum {
-        KEYBOARD_ANY  = 0x0000,
-        KEYBOARD_NOKEYS  = 0x0001,
-        KEYBOARD_QWERTY  = 0x0002,
-        KEYBOARD_12KEY  = 0x0003,
+        KEYBOARD_ANY  = ACONFIGURATION_KEYBOARD_ANY,
+        KEYBOARD_NOKEYS  = ACONFIGURATION_KEYBOARD_NOKEYS,
+        KEYBOARD_QWERTY  = ACONFIGURATION_KEYBOARD_QWERTY,
+        KEYBOARD_12KEY  = ACONFIGURATION_KEYBOARD_12KEY,
     };
     
     enum {
-        NAVIGATION_ANY  = 0x0000,
-        NAVIGATION_NONAV  = 0x0001,
-        NAVIGATION_DPAD  = 0x0002,
-        NAVIGATION_TRACKBALL  = 0x0003,
-        NAVIGATION_WHEEL  = 0x0004,
+        NAVIGATION_ANY  = ACONFIGURATION_NAVIGATION_ANY,
+        NAVIGATION_NONAV  = ACONFIGURATION_NAVIGATION_NONAV,
+        NAVIGATION_DPAD  = ACONFIGURATION_NAVIGATION_DPAD,
+        NAVIGATION_TRACKBALL  = ACONFIGURATION_NAVIGATION_TRACKBALL,
+        NAVIGATION_WHEEL  = ACONFIGURATION_NAVIGATION_WHEEL,
     };
     
     enum {
         MASK_KEYSHIDDEN = 0x0003,
-        KEYSHIDDEN_ANY = 0x0000,
-        KEYSHIDDEN_NO = 0x0001,
-        KEYSHIDDEN_YES = 0x0002,
-        KEYSHIDDEN_SOFT = 0x0003,
+        KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY,
+        KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO,
+        KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES,
+        KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT,
     };
     
     enum {
         MASK_NAVHIDDEN = 0x000c,
-        NAVHIDDEN_ANY = 0x0000,
-        NAVHIDDEN_NO = 0x0004,
-        NAVHIDDEN_YES = 0x0008,
+        SHIFT_NAVHIDDEN = 2,
+        NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN,
+        NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN,
+        NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN,
     };
     
     union {
@@ -929,32 +932,34 @@
     enum {
         // screenLayout bits for screen size class.
         MASK_SCREENSIZE = 0x0f,
-        SCREENSIZE_ANY  = 0x00,
-        SCREENSIZE_SMALL = 0x01,
-        SCREENSIZE_NORMAL = 0x02,
-        SCREENSIZE_LARGE = 0x03,
-        SCREENSIZE_XLARGE = 0x04,
+        SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY,
+        SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL,
+        SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL,
+        SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE,
+        SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE,
         
         // screenLayout bits for wide/long screen variation.
         MASK_SCREENLONG = 0x30,
-        SCREENLONG_ANY = 0x00,
-        SCREENLONG_NO = 0x10,
-        SCREENLONG_YES = 0x20,
+        SHIFT_SCREENLONG = 4,
+        SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG,
+        SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG,
+        SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG,
     };
     
     enum {
         // uiMode bits for the mode type.
         MASK_UI_MODE_TYPE = 0x0f,
-        UI_MODE_TYPE_ANY = 0x00,
-        UI_MODE_TYPE_NORMAL = 0x01,
-        UI_MODE_TYPE_DESK = 0x02,
-        UI_MODE_TYPE_CAR = 0x03,
+        UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY,
+        UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL,
+        UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK,
+        UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR,
 
         // uiMode bits for the night switch.
         MASK_UI_MODE_NIGHT = 0x30,
-        UI_MODE_NIGHT_ANY = 0x00,
-        UI_MODE_NIGHT_NO = 0x10,
-        UI_MODE_NIGHT_YES = 0x20,
+        SHIFT_UI_MODE_NIGHT = 4,
+        UI_MODE_NIGHT_ANY = ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT,
+        UI_MODE_NIGHT_NO = ACONFIGURATION_UI_MODE_NIGHT_NO << SHIFT_UI_MODE_NIGHT,
+        UI_MODE_NIGHT_YES = ACONFIGURATION_UI_MODE_NIGHT_YES << SHIFT_UI_MODE_NIGHT,
     };
 
     union {
@@ -1023,19 +1028,19 @@
     // match the corresponding ones in android.content.pm.ActivityInfo and
     // attrs_manifest.xml.
     enum {
-        CONFIG_MCC = 0x0001,
-        CONFIG_MNC = 0x0002,
-        CONFIG_LOCALE = 0x0004,
-        CONFIG_TOUCHSCREEN = 0x0008,
-        CONFIG_KEYBOARD = 0x0010,
-        CONFIG_KEYBOARD_HIDDEN = 0x0020,
-        CONFIG_NAVIGATION = 0x0040,
-        CONFIG_ORIENTATION = 0x0080,
-        CONFIG_DENSITY = 0x0100,
-        CONFIG_SCREEN_SIZE = 0x0200,
-        CONFIG_VERSION = 0x0400,
-        CONFIG_SCREEN_LAYOUT = 0x0800,
-        CONFIG_UI_MODE = 0x1000
+        CONFIG_MCC = ACONFIGURATION_MCC,
+        CONFIG_MNC = ACONFIGURATION_MCC,
+        CONFIG_LOCALE = ACONFIGURATION_LOCALE,
+        CONFIG_TOUCHSCREEN = ACONFIGURATION_TOUCHSCREEN,
+        CONFIG_KEYBOARD = ACONFIGURATION_KEYBOARD,
+        CONFIG_KEYBOARD_HIDDEN = ACONFIGURATION_KEYBOARD_HIDDEN,
+        CONFIG_NAVIGATION = ACONFIGURATION_NAVIGATION,
+        CONFIG_ORIENTATION = ACONFIGURATION_ORIENTATION,
+        CONFIG_DENSITY = ACONFIGURATION_DENSITY,
+        CONFIG_SCREEN_SIZE = ACONFIGURATION_SCREEN_SIZE,
+        CONFIG_VERSION = ACONFIGURATION_VERSION,
+        CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT,
+        CONFIG_UI_MODE = ACONFIGURATION_UI_MODE
     };
     
     // Compare two configuration, returning CONFIG_* flags set for each value
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 8d00e85..e807aba 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -69,16 +69,16 @@
     int width = (int) glyph->mBitmapWidth;
     int height = (int) glyph->mBitmapHeight;
 
-    if(bounds->bottom > nPenY) {
+    if (bounds->bottom > nPenY) {
         bounds->bottom = nPenY;
     }
-    if(bounds->left > nPenX) {
+    if (bounds->left > nPenX) {
         bounds->left = nPenX;
     }
-    if(bounds->right < nPenX + width) {
+    if (bounds->right < nPenX + width) {
         bounds->right = nPenX + width;
     }
-    if(bounds->top < nPenY + height) {
+    if (bounds->top < nPenY + height) {
         bounds->top = nPenY + height;
     }
 }
@@ -102,7 +102,7 @@
 }
 
 void Font::drawCachedGlyph(CachedGlyphInfo *glyph, int x, int y,
-                             uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+        uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
     int nPenX = x + glyph->mBitmapLeft;
     int nPenY = y + glyph->mBitmapTop;
 
@@ -116,7 +116,7 @@
     int32_t bX = 0, bY = 0;
     for (cacheX = glyph->mStartX, bX = nPenX; cacheX < endX; cacheX++, bX++) {
         for (cacheY = glyph->mStartY, bY = nPenY; cacheY < endY; cacheY++, bY++) {
-            if(bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) {
+            if (bX < 0 || bY < 0 || bX >= (int32_t)bitmapW || bY >= (int32_t)bitmapH) {
                 LOGE("Skipping invalid index");
                 continue;
             }
@@ -143,22 +143,19 @@
 }
 
 void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
-                       int numGlyphs, int x, int y,
-                       uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
-    if(bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
-        renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP,
-                   bitmap, bitmapW, bitmapH, NULL);
-    }
-    else {
-        renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER,
-                   NULL, 0, 0, NULL);
+        int numGlyphs, int x, int y, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH) {
+    if (bitmap != NULL && bitmapW > 0 && bitmapH > 0) {
+        renderUTF(paint, text, start, len, numGlyphs, x, y, BITMAP, bitmap,
+                bitmapW, bitmapH, NULL);
+    } else {
+        renderUTF(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, NULL, 0, 0, NULL);
     }
 
 }
 
 void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
-                       int numGlyphs, Rect *bounds) {
-    if(bounds == NULL) {
+        int numGlyphs, Rect *bounds) {
+    if (bounds == NULL) {
         LOGE("No return rectangle provided to measure text");
         return;
     }
@@ -167,9 +164,8 @@
 }
 
 void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
-                       int numGlyphs, int x, int y, RenderMode mode,
-                       uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH,
-                       Rect *bounds) {
+        int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
+        uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) {
     if (numGlyphs == 0 || text == NULL || len == 0) {
         return;
     }
@@ -185,7 +181,7 @@
     while (glyphsLeft > 0) {
         int32_t utfChar = SkUTF16_NextUnichar((const uint16_t**) &text);
 
-        // Reached the end of the string or encountered
+        // Reached the end of the string
         if (utfChar < 0) {
             break;
         }
@@ -422,7 +418,7 @@
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
     // Initialize texture dimentions
     glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mCacheWidth, mCacheHeight, 0,
-                  GL_ALPHA, GL_UNSIGNED_BYTE, 0);
+            GL_ALPHA, GL_UNSIGNED_BYTE, 0);
 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -527,7 +523,6 @@
 }
 
 void FontRenderer::issueDrawCommand() {
-
     checkTextureUpdate();
 
     float* vtx = mTextMeshPtr;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 5d30b1a..aaf7564 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -435,6 +435,7 @@
         return;
     }
 
+    glActiveTexture(GL_TEXTURE0);
     const Texture* texture = mTextureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
@@ -451,6 +452,7 @@
         return;
     }
 
+    glActiveTexture(GL_TEXTURE0);
     const Texture* texture = mTextureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
@@ -466,6 +468,7 @@
         return;
     }
 
+    glActiveTexture(GL_TEXTURE0);
     const Texture* texture = mTextureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
@@ -491,6 +494,7 @@
         return;
     }
 
+    glActiveTexture(GL_TEXTURE0);
     const Texture* texture = mTextureCache.get(bitmap);
     if (!texture) return;
     const AutoTexture autoCleanup(texture);
@@ -538,13 +542,17 @@
     drawColorRect(left, top, right, bottom, color, mode);
 }
 
+#define kStdStrikeThru_Offset   (-6.0f / 21.0f)
+#define kStdUnderline_Offset    (1.0f / 9.0f)
+#define kStdUnderline_Thickness (1.0f / 18.0f)
+
 void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
         float x, float y, SkPaint* paint) {
     if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
         return;
     }
 
-    float length;
+    float length = -1.0f;
     switch (paint->getTextAlign()) {
         case SkPaint::kCenter_Align:
             length = paint->measureText(text, bytesCount);
@@ -607,13 +615,56 @@
         mColorFilter->setupProgram(mCurrentProgram);
     }
 
-    // TODO: Implement scale properly
     const Rect& clip = mSnapshot->getLocalClip();
     mFontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize());
     mFontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
 
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
     glDisableVertexAttribArray(texCoordsSlot);
+
+    // Handle underline and strike-through
+    uint32_t flags = paint->getFlags();
+    if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) {
+        float underlineWidth = length;
+        // If length is > 0.0f, we already measured the text for the text alignment
+        if (length <= 0.0f) {
+            underlineWidth = paint->measureText(text, bytesCount);
+        }
+
+        float offsetX = 0;
+        switch (paint->getTextAlign()) {
+            case SkPaint::kCenter_Align:
+                offsetX = underlineWidth * 0.5f;
+                break;
+            case SkPaint::kRight_Align:
+                offsetX = underlineWidth;
+                break;
+            default:
+                break;
+        }
+
+        if (underlineWidth > 0.0f) {
+            float textSize = paint->getTextSize();
+            float height = textSize * kStdUnderline_Thickness;
+
+            float left = x - offsetX;
+            float top = 0.0f;
+            float right = left + underlineWidth;
+            float bottom = 0.0f;
+
+            if (flags & SkPaint::kUnderlineText_Flag) {
+                top = y + textSize * kStdUnderline_Offset;
+                bottom = top + height;
+                drawRect(left, top, right, bottom, paint);
+            }
+
+            if (flags & SkPaint::kStrikeThruText_Flag) {
+                top = y + textSize * kStdStrikeThru_Offset;
+                bottom = top + height;
+                drawRect(left, top, right, bottom, paint);
+            }
+        }
+    }
 }
 
 void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 4a01ffa..9a22dc0 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -162,7 +162,8 @@
     glGenTextures(1, &texture->id);
 
     glBindTexture(GL_TEXTURE_2D, texture->id);
-    glPixelStorei(GL_UNPACK_ALIGNMENT, bitmap.bytesPerPixel());
+    // Textures are Alpha8
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
     texture->blend = true;
     glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap.rowBytesAsPixels(), texture->height, 0,
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 8a97b4c..39fe85a 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -137,7 +137,7 @@
 // PorterDuff snippets
 ///////////////////////////////////////////////////////////////////////////////
 
-const char* gPorterDuff[12] = {
+const char* gBlendOps[18] = {
         // Clear
         "return vec4(0.0, 0.0, 0.0, 0.0);\n",
         // Src
@@ -161,8 +161,26 @@
         // 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, 1.0, "
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb, "
                 "src.a + dst.a - 2.0 * src.a * dst.a);\n",
+        // Add
+        "return min(src + dst, 1.0);\n",
+        // Multiply
+        "return src * dst;\n",
+        // Screen
+        "return src + dst - src * dst;\n",
+        // Overlay
+        "return clamp(vec4(mix("
+                "2.0 * src.rgb * dst.rgb + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), "
+                "src.a * dst.a - 2.0 * (dst.a - dst.rgb) * (src.a - src.rgb) + src.rgb * (1.0 - dst.a) + dst.rgb * (1.0 - src.a), "
+                "step(dst.a, 2.0 * dst.rgb)), "
+                "src.a + dst.a - src.a * dst.a), 0.0, 1.0);\n",
+        // Darken
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + "
+                "min(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n",
+        // Lighten
+        "return vec4(src.rgb * (1.0 - dst.a) + (1.0 - src.a) * dst.rgb + "
+                "max(src.rgb * dst.a, dst.rgb * src.a), src.a + dst.a - src.a * dst.a);\n",
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -292,10 +310,10 @@
 
     // Generate required functions
     if (description.hasGradient && description.hasBitmap) {
-        generatePorterDuffBlend(shader, "blendShaders", description.shadersMode);
+        generateBlend(shader, "blendShaders", description.shadersMode);
     }
     if (description.colorOp == ProgramDescription::kColorBlend) {
-        generatePorterDuffBlend(shader, "blendColors", description.colorMode);
+        generateBlend(shader, "blendColors", description.colorMode);
     }
     if (description.isBitmapNpot) {
         generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
@@ -354,13 +372,12 @@
     return shader;
 }
 
-void ProgramCache::generatePorterDuffBlend(String8& shader, const char* name,
-        SkXfermode::Mode mode) {
+void ProgramCache::generateBlend(String8& shader, const char* name, SkXfermode::Mode mode) {
     shader.append("\nvec4 ");
     shader.append(name);
     shader.append("(vec4 src, vec4 dst) {\n");
     shader.append("    ");
-    shader.append(gPorterDuff[mode]);
+    shader.append(gBlendOps[mode]);
     shader.append("}\n");
 }
 
@@ -376,6 +393,9 @@
     }
     shader.append("    return vec2(");
     switch (wrapS) {
+        case GL_CLAMP_TO_EDGE:
+            shader.append("texCoords.x");
+            break;
         case GL_REPEAT:
             shader.append("mod(texCoords.x, 1.0)");
             break;
@@ -385,6 +405,9 @@
     }
     shader.append(", ");
     switch (wrapT) {
+        case GL_CLAMP_TO_EDGE:
+            shader.append("texCoords.y");
+            break;
         case GL_REPEAT:
             shader.append("mod(texCoords.y, 1.0)");
             break;
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index a1a4a0e..54850ee 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -35,7 +35,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // Debug
-#define DEBUG_PROGRAM_CACHE 0
+#define DEBUG_PROGRAM_CACHE 1
 
 // Debug
 #if DEBUG_PROGRAM_CACHE
@@ -57,9 +57,9 @@
 #define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
 #define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
 
-// Support only the 12 Porter-Duff modes for now
-#define PROGRAM_MAX_XFERMODE 0xC
-#define PROGRAM_XFERMODE_SHADER_SHIFT 24
+// Encode the xfermodes on 6 bits
+#define PROGRAM_MAX_XFERMODE 0x1f
+#define PROGRAM_XFERMODE_SHADER_SHIFT 26
 #define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
 
 #define PROGRAM_BITMAP_WRAPS_SHIFT 9
@@ -177,7 +177,7 @@
     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);
+    void generateBlend(String8& shader, const char* name, SkXfermode::Mode mode);
     void generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT);
 
     void printLongString(const String8& shader) const;
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 42c0621..3569d6a 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -89,7 +89,8 @@
     description.hasBitmap = true;
     // The driver does not support non-power of two mirrored/repeated
     // textures, so do it ourselves
-    if (!extensions.hasNPot() && !isPowerOfTwo(width) && !isPowerOfTwo(height)) {
+    if (!extensions.hasNPot() && (!isPowerOfTwo(width) || !isPowerOfTwo(height)) &&
+            (mTileX != SkShader::kClamp_TileMode || mTileY != SkShader::kClamp_TileMode)) {
         description.isBitmapNpot = true;
         description.bitmapWrapS = gTileModes[mTileX];
         description.bitmapWrapT = gTileModes[mTileY];
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
index 008c8b5..3c69289 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/vertical_blur.rs
@@ -8,52 +8,22 @@
     const uchar4 *input = (const uchar4 *)rsGetElementAt(fs->ain, x, 0);
 
     float3 blurredPixel = 0;
-    float3 currentPixel = 0;
-
     const float *gPtr = fs->gaussian;
     if ((y > fs->radius) && (y < (fs->height - fs->radius))) {
         const uchar4 *i = input + ((y - fs->radius) * fs->width);
         for(int r = -fs->radius; r <= fs->radius; r ++) {
-            currentPixel.x = (float)(i->x);
-            currentPixel.y = (float)(i->y);
-            currentPixel.z = (float)(i->z);
-            blurredPixel += currentPixel * gPtr[0];
+            blurredPixel += convert_float3(i->xyz) * gPtr[0];
             gPtr++;
             i += fs->width;
         }
     } else {
         for(int r = -fs->radius; r <= fs->radius; r ++) {
-    #if 1
-            int validH = y + r;
-            // Clamp to zero and width
-            if(validH < 0) {
-                validH = 0;
-            }
-            if(validH > fs->height - 1) {
-                validH = fs->height - 1;
-            }
-
+            int validH = rsClamp(y + r, (uint)0, (uint)(fs->height - 1));
             const uchar4 *i = input + validH * fs->width;
-            //const uchar4 *i = (const uchar4 *)rsGetElementAt(fs->ain, x, validH);
-
-            currentPixel.x = (float)(i->x);
-            currentPixel.y = (float)(i->y);
-            currentPixel.z = (float)(i->z);
-            blurredPixel += currentPixel * gPtr[0];
+            blurredPixel += convert_float3(i->xyz) * gPtr[0];
             gPtr++;
-    #else
-            int validH = rsClamp(y + r, 0, height - 1);
-            validH -= y;
-            uchar4 *i = input + validH * width + x;
-            blurredPixel.xyz += convert_float3(i->xyz) * gPtr[0];
-            gPtr++;
-    #endif
         }
     }
-
-    //output->xyz = convert_uchar3(blurredPixel.xyz);
-    output->x = (uint8_t)blurredPixel.x;
-    output->y = (uint8_t)blurredPixel.y;
-    output->z = (uint8_t)blurredPixel.z;
+    output->xyz = convert_uchar3(blurredPixel);
 }
 
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index 6560101..d62fa55 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -234,6 +234,12 @@
         LOGE("Allocation::data called with mismatched size expected %i, got %i", size, sizeBytes);
         return;
     }
+
+    if (mType->getElement()->getHasReferences()) {
+        incRefs(data, sizeBytes / mType->getElement()->getSizeBytes());
+        decRefs(mPtr, sizeBytes / mType->getElement()->getSizeBytes());
+    }
+
     memcpy(mPtr, data, size);
     sendDirty();
     mUploadDefered = true;
@@ -256,6 +262,12 @@
         mType->dumpLOGV("type info");
         return;
     }
+
+    if (mType->getElement()->getHasReferences()) {
+        incRefs(data, count);
+        decRefs(ptr, count);
+    }
+
     memcpy(ptr, data, size);
     sendDirty();
     mUploadDefered = true;
@@ -279,6 +291,10 @@
 
     for (uint32_t line=yoff; line < (yoff+h); line++) {
         uint8_t * ptr = static_cast<uint8_t *>(mPtr);
+        if (mType->getElement()->getHasReferences()) {
+            incRefs(src, w);
+            decRefs(dst, w);
+        }
         memcpy(dst, src, lineSize);
         src += lineSize;
         dst += destW * eSize;
@@ -387,6 +403,32 @@
     }
 }
 
+void Allocation::incRefs(const void *ptr, size_t ct) const
+{
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    const Element *e = mType->getElement();
+    uint32_t stride = e->getSizeBytes();
+
+    while (ct > 0) {
+        e->incRefs(p);
+        ct --;
+        p += stride;
+    }
+}
+
+void Allocation::decRefs(const void *ptr, size_t ct) const
+{
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    const Element *e = mType->getElement();
+    uint32_t stride = e->getSizeBytes();
+
+    while (ct > 0) {
+        e->decRefs(p);
+        ct --;
+        p += stride;
+    }
+}
+
 /////////////////
 //
 
diff --git a/libs/rs/rsAllocation.h b/libs/rs/rsAllocation.h
index 8273165..177d5a4 100644
--- a/libs/rs/rsAllocation.h
+++ b/libs/rs/rsAllocation.h
@@ -81,6 +81,9 @@
     bool getIsTexture() const {return mIsTexture;}
     bool getIsBufferObject() const {return mIsVertexBuffer;}
 
+    void incRefs(const void *ptr, size_t ct) const;
+    void decRefs(const void *ptr, size_t ct) const;
+
 protected:
     void sendDirty() const;
 
diff --git a/libs/rs/rsComponent.cpp b/libs/rs/rsComponent.cpp
index 8e509ad..fbaa75f 100644
--- a/libs/rs/rsComponent.cpp
+++ b/libs/rs/rsComponent.cpp
@@ -161,6 +161,10 @@
     mBits = mTypeBits * mVectorSize;
 }
 
+bool Component::isReference() const
+{
+    return (mType >= RS_TYPE_ELEMENT);
+}
 
 
 
diff --git a/libs/rs/rsComponent.h b/libs/rs/rsComponent.h
index 15fd5dd..a775051 100644
--- a/libs/rs/rsComponent.h
+++ b/libs/rs/rsComponent.h
@@ -51,6 +51,8 @@
     void serialize(OStream *stream) const;
     void loadFromStream(IStream *stream);
 
+    bool isReference() const;
+
 protected:
     RsDataType mType;
     RsDataKind mKind;
diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp
index 37b8bd6..05902f9 100644
--- a/libs/rs/rsElement.cpp
+++ b/libs/rs/rsElement.cpp
@@ -34,6 +34,7 @@
     mAllocLine = __LINE__;
     mFields = NULL;
     mFieldCount = 0;
+    mHasReference = false;
 }
 
 
@@ -53,6 +54,7 @@
     delete [] mFields;
     mFields = NULL;
     mFieldCount = 0;
+    mHasReference = false;
 }
 
 size_t Element::getSizeBits() const
@@ -68,15 +70,6 @@
     return total;
 }
 
-size_t Element::getFieldOffsetBits(uint32_t componentNumber) const
-{
-    size_t offset = 0;
-    for (uint32_t ct = 0; ct < componentNumber; ct++) {
-        offset += mFields[ct].e->mBits;
-    }
-    return offset;
-}
-
 void Element::dumpLOGV(const char *prefix) const
 {
     ObjectBase::dumpLOGV(prefix);
@@ -124,11 +117,14 @@
 
     elem->mFieldCount = stream->loadU32();
     if(elem->mFieldCount) {
+        uint32_t offset = 0;
         elem->mFields = new ElementField_t [elem->mFieldCount];
         for(uint32_t ct = 0; ct < elem->mFieldCount; ct ++) {
             stream->loadString(&elem->mFields[ct].name);
             Element *fieldElem = Element::createFromStream(rsc, stream);
             elem->mFields[ct].e.set(fieldElem);
+            elem->mFields[ct].offsetBits = offset;
+            offset += fieldElem->getSizeBits();
         }
     }
 
@@ -193,6 +189,7 @@
     Element *e = new Element(rsc);
     e->mComponent.set(dt, dk, isNorm, vecSize);
     e->mBits = e->mComponent.getBits();
+    e->mHasReference = e->mComponent.isReference();
     rsc->mStateElement.mElements.push(e);
     return e;
 }
@@ -223,9 +220,16 @@
     Element *e = new Element(rsc);
     e->mFields = new ElementField_t [count];
     e->mFieldCount = count;
+    size_t bits = 0;
     for (size_t ct=0; ct < count; ct++) {
         e->mFields[ct].e.set(ein[ct]);
         e->mFields[ct].name.setTo(nin[ct], lengths[ct]);
+        e->mFields[ct].offsetBits = bits;
+        bits += ein[ct]->getSizeBits();
+
+        if (ein[ct]->mHasReference) {
+            e->mHasReference = true;
+        }
     }
 
     rsc->mStateElement.mElements.push(e);
@@ -251,6 +255,43 @@
     return s;
 }
 
+void Element::incRefs(const void *ptr) const
+{
+    if (!mFieldCount) {
+        if (mComponent.isReference()) {
+            ObjectBase *const*obp = static_cast<ObjectBase *const*>(ptr);
+            ObjectBase *ob = obp[0];
+            ob->incSysRef();
+        }
+        return;
+    }
+
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    for (uint32_t i=0; i < mFieldCount; i++) {
+        if (mFields[i].e->mHasReference) {
+            mFields[i].e->incRefs(&p[mFields[i].offsetBits >> 3]);
+        }
+    }
+}
+
+void Element::decRefs(const void *ptr) const
+{
+    if (!mFieldCount) {
+        if (mComponent.isReference()) {
+            ObjectBase *const*obp = static_cast<ObjectBase *const*>(ptr);
+            ObjectBase *ob = obp[0];
+            ob->decSysRef();
+        }
+        return;
+    }
+
+    const uint8_t *p = static_cast<const uint8_t *>(ptr);
+    for (uint32_t i=0; i < mFieldCount; i++) {
+        if (mFields[i].e->mHasReference) {
+            mFields[i].e->decRefs(&p[mFields[i].offsetBits >> 3]);
+        }
+    }
+}
 
 
 ElementState::ElementState()
diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h
index 90e7cc8..b5dad7a2 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -40,9 +40,11 @@
         return (getSizeBits() + 7) >> 3;
     }
 
-    size_t getFieldOffsetBits(uint32_t componentNumber) const;
+    size_t getFieldOffsetBits(uint32_t componentNumber) const {
+        return mFields[componentNumber].offsetBits;
+    }
     size_t getFieldOffsetBytes(uint32_t componentNumber) const {
-        return (getFieldOffsetBits(componentNumber) + 7) >> 3;
+        return mFields[componentNumber].offsetBits >> 3;
     }
 
     uint32_t getFieldCount() const {return mFieldCount;}
@@ -66,6 +68,10 @@
     static const Element * create(Context *rsc, size_t count, const Element **,
                             const char **, const size_t * lengths);
 
+    void incRefs(const void *) const;
+    void decRefs(const void *) const;
+    bool getHasReferences() const {return mHasReference;}
+
 protected:
     // deallocate any components that are part of this element.
     void clear();
@@ -73,9 +79,11 @@
     typedef struct {
         String8 name;
         ObjectBaseRef<const Element> e;
+        uint32_t offsetBits;
     } ElementField_t;
     ElementField_t *mFields;
     size_t mFieldCount;
+    bool mHasReference;
 
 
     Element(Context *);
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 6618702..5f5a4ac 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -1080,6 +1080,14 @@
             1, & pointerId, pointerCoords, mXPrecision, mYPrecision, downTime);
 }
 
+int32_t TrackballInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+    if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
+        return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+    } else {
+        return AKEY_STATE_UNKNOWN;
+    }
+}
+
 
 // --- TouchInputMapper ---
 
diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp
index 60a0d82..e09e755 100644
--- a/libs/utils/AssetManager.cpp
+++ b/libs/utils/AssetManager.cpp
@@ -232,6 +232,12 @@
     }
 }
 
+void AssetManager::getConfiguration(ResTable_config* outConfig) const
+{
+    AutoMutex _l(mLock);
+    *outConfig = *mConfig;
+}
+
 /*
  * Open an asset.
  *
diff --git a/media/java/android/media/AudioEffect.java b/media/java/android/media/AudioEffect.java
index aed29c3..35038fa 100644
--- a/media/java/android/media/AudioEffect.java
+++ b/media/java/android/media/AudioEffect.java
@@ -101,15 +101,15 @@
     public static final int STATE_INITIALIZED = 1;
 
     // to keep in sync with
-    // frameworks/base/media/jni/audioeffect/android_media_AudioEffect.cpp
-    /**
-     * Event id for engine state change notification.
-     */
-    public static final int NATIVE_EVENT_ENABLED_STATUS = 0;
+    // frameworks/base/include/media/AudioEffect.h
     /**
      * Event id for engine control ownership change notification.
      */
-    public static final int NATIVE_EVENT_CONTROL_STATUS = 1;
+    public static final int NATIVE_EVENT_CONTROL_STATUS = 0;
+    /**
+     * Event id for engine state change notification.
+     */
+    public static final int NATIVE_EVENT_ENABLED_STATUS = 1;
     /**
      * Event id for engine parameter change notification.
      */
@@ -795,7 +795,7 @@
     // Interface definitions
     // --------------------
     /**
-     * The OnParameterChangeListener interface defines a method called by the AudioEffect
+     * The OnEnableStatusChangeListener interface defines a method called by the AudioEffect
      * when a the enabled state of the effect engine was changed by the controlling application.
      */
     public interface OnEnableStatusChangeListener {
@@ -922,7 +922,6 @@
         if (effect == null) {
             return;
         }
-
         if (effect.mNativeEventHandler != null) {
             Message m = effect.mNativeEventHandler.obtainMessage(what, arg1,
                     arg2, obj);
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 9212708..41d2cc5 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -239,6 +239,9 @@
     //  independently change its priority)
     private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver();
 
+    // Used to alter media button redirection when the phone is ringing.
+    private boolean mIsRinging = false;
+
     // Devices currently connected
     private HashMap <Integer, String> mConnectedDevices = new HashMap <Integer, String>();
 
@@ -1956,11 +1959,16 @@
 
     private final static Object mAudioFocusLock = new Object();
 
+    private final static Object mRingingLock = new Object();
+
     private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
         @Override
         public void onCallStateChanged(int state, String incomingNumber) {
             if (state == TelephonyManager.CALL_STATE_RINGING) {
                 //Log.v(TAG, " CALL_STATE_RINGING");
+                synchronized(mRingingLock) {
+                    mIsRinging = true;
+                }
                 int ringVolume = AudioService.this.getStreamVolume(AudioManager.STREAM_RING);
                 if (ringVolume > 0) {
                     requestAudioFocus(AudioManager.STREAM_RING,
@@ -1970,12 +1978,18 @@
                 }
             } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
                 //Log.v(TAG, " CALL_STATE_OFFHOOK");
+                synchronized(mRingingLock) {
+                    mIsRinging = false;
+                }
                 requestAudioFocus(AudioManager.STREAM_RING,
                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
                         null, null /* both allowed to be null only for this clientId */,
                         IN_VOICE_COMM_FOCUS_ID /*clientId*/);
             } else if (state == TelephonyManager.CALL_STATE_IDLE) {
                 //Log.v(TAG, " CALL_STATE_IDLE");
+                synchronized(mRingingLock) {
+                    mIsRinging = false;
+                }
                 abandonAudioFocus(null, IN_VOICE_COMM_FOCUS_ID);
             }
         }
@@ -2243,9 +2257,11 @@
                 // if in a call or ringing, do not break the current phone app behavior
                 // TODO modify this to let the phone app specifically get the RC focus
                 //      add modify the phone app to take advantage of the new API
-                if ((getMode() == AudioSystem.MODE_IN_CALL) ||
-                        (getMode() == AudioSystem.MODE_RINGTONE)) {
-                    return;
+                synchronized(mRingingLock) {
+                    if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) ||
+                            (getMode() == AudioSystem.MODE_RINGTONE) ) {
+                        return;
+                    }
                 }
                 synchronized(mRCStack) {
                     if (!mRCStack.empty()) {
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index 37f9f2c..88cce46 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -220,6 +220,50 @@
         return -1;
     }
 
+    private int[] getSupportedPlaybackFormats() {
+        return new int[] {
+            Mtp.Object.FORMAT_ASSOCIATION,
+            Mtp.Object.FORMAT_MP3,
+            Mtp.Object.FORMAT_MPEG,
+            Mtp.Object.FORMAT_EXIF_JPEG,
+            Mtp.Object.FORMAT_TIFF_EP,
+            Mtp.Object.FORMAT_GIF,
+            Mtp.Object.FORMAT_JFIF,
+            Mtp.Object.FORMAT_PNG,
+            Mtp.Object.FORMAT_TIFF,
+            Mtp.Object.FORMAT_WMA,
+            Mtp.Object.FORMAT_OGG,
+            Mtp.Object.FORMAT_AAC,
+            Mtp.Object.FORMAT_MP4_CONTAINER,
+            Mtp.Object.FORMAT_MP2,
+            Mtp.Object.FORMAT_3GP_CONTAINER,
+            Mtp.Object.FORMAT_ABSTRACT_AV_PLAYLIST,
+            Mtp.Object.FORMAT_WPL_PLAYLIST,
+            Mtp.Object.FORMAT_M3U_PLAYLIST,
+            Mtp.Object.FORMAT_PLS_PLAYLIST,
+        };
+    }
+
+    private int[] getSupportedCaptureFormats() {
+        // no capture formats yet
+        return null;
+    }
+
+    private int[] getSupportedObjectProperties(int handle) {
+        return new int[] {
+            Mtp.Object.PROPERTY_STORAGE_ID,
+            Mtp.Object.PROPERTY_OBJECT_FORMAT,
+            Mtp.Object.PROPERTY_OBJECT_SIZE,
+            Mtp.Object.PROPERTY_OBJECT_FILE_NAME,
+            Mtp.Object.PROPERTY_PARENT_OBJECT,
+        };
+    }
+
+    private int[] getSupportedDeviceProperties() {
+        // no device properties yet
+        return null;
+    }
+
     private int getObjectProperty(int handle, int property,
                             long[] outIntValue, char[] outStringValue) {
         Log.d(TAG, "getObjectProperty: " + property);
diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp
index bfdc872..abbea30 100644
--- a/media/jni/android_media_MtpDatabase.cpp
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -40,6 +40,10 @@
 static jmethodID method_endSendObject;
 static jmethodID method_getObjectList;
 static jmethodID method_getNumObjects;
+static jmethodID method_getSupportedPlaybackFormats;
+static jmethodID method_getSupportedCaptureFormats;
+static jmethodID method_getSupportedObjectProperties;
+static jmethodID method_getSupportedDeviceProperties;
 static jmethodID method_getObjectProperty;
 static jmethodID method_getObjectInfo;
 static jmethodID method_getObjectFilePath;
@@ -87,6 +91,13 @@
                                             MtpObjectFormat format,
                                             MtpObjectHandle parent);
 
+    // callee should delete[] the results from these
+    // results can be NULL
+    virtual MtpObjectFormatList*    getSupportedPlaybackFormats();
+    virtual MtpObjectFormatList*    getSupportedCaptureFormats();
+    virtual MtpObjectPropertyList*  getSupportedObjectProperties(MtpObjectFormat format);
+    virtual MtpDevicePropertyList*  getSupportedDeviceProperties();
+
     virtual MtpResponseCode         getObjectProperty(MtpObjectHandle handle,
                                             MtpObjectProperty property,
                                             MtpDataPacket& packet);
@@ -190,6 +201,66 @@
                 (jint)storageID, (jint)format, (jint)parent);
 }
 
+MtpObjectFormatList* MyMtpDatabase::getSupportedPlaybackFormats() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedPlaybackFormats);
+    if (!array)
+        return NULL;
+    MtpObjectFormatList* list = new MtpObjectFormatList();
+    jint* formats = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(formats[i]);
+   env->ReleaseIntArrayElements(array, formats, 0);
+   return list;
+}
+
+MtpObjectFormatList* MyMtpDatabase::getSupportedCaptureFormats() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedCaptureFormats);
+    if (!array)
+        return NULL;
+    MtpObjectFormatList* list = new MtpObjectFormatList();
+    jint* formats = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(formats[i]);
+   env->ReleaseIntArrayElements(array, formats, 0);
+   return list;
+}
+
+MtpObjectPropertyList* MyMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedObjectProperties, (jint)format);
+    if (!array)
+        return NULL;
+    MtpObjectPropertyList* list = new MtpObjectPropertyList();
+    jint* properties = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(properties[i]);
+   env->ReleaseIntArrayElements(array, properties, 0);
+   return list;
+}
+
+MtpDevicePropertyList* MyMtpDatabase::getSupportedDeviceProperties() {
+    JNIEnv* env = AndroidRuntime::getJNIEnv();
+    jintArray array = (jintArray)env->CallObjectMethod(mDatabase,
+            method_getSupportedDeviceProperties);
+    if (!array)
+        return NULL;
+    MtpDevicePropertyList* list = new MtpDevicePropertyList();
+    jint* properties = env->GetIntArrayElements(array, 0);
+    jsize length = env->GetArrayLength(array);
+    for (int i = 0; i < length; i++)
+        list->push(properties[i]);
+   env->ReleaseIntArrayElements(array, properties, 0);
+   return list;
+}
+
 MtpResponseCode MyMtpDatabase::getObjectProperty(MtpObjectHandle handle,
                                             MtpObjectProperty property,
                                             MtpDataPacket& packet) {
@@ -460,6 +531,26 @@
         LOGE("Can't find getNumObjects");
         return -1;
     }
+    method_getSupportedPlaybackFormats = env->GetMethodID(clazz, "getSupportedPlaybackFormats", "()[I");
+    if (method_getSupportedPlaybackFormats == NULL) {
+        LOGE("Can't find getSupportedPlaybackFormats");
+        return -1;
+    }
+    method_getSupportedCaptureFormats = env->GetMethodID(clazz, "getSupportedCaptureFormats", "()[I");
+    if (method_getSupportedCaptureFormats == NULL) {
+        LOGE("Can't find getSupportedCaptureFormats");
+        return -1;
+    }
+    method_getSupportedObjectProperties = env->GetMethodID(clazz, "getSupportedObjectProperties", "(I)[I");
+    if (method_getSupportedObjectProperties == NULL) {
+        LOGE("Can't find getSupportedObjectProperties");
+        return -1;
+    }
+    method_getSupportedDeviceProperties = env->GetMethodID(clazz, "getSupportedDeviceProperties", "()[I");
+    if (method_getSupportedDeviceProperties == NULL) {
+        LOGE("Can't find getSupportedDeviceProperties");
+        return -1;
+    }
     method_getObjectProperty = env->GetMethodID(clazz, "getObjectProperty", "(II[J[C)I");
     if (method_getObjectProperty == NULL) {
         LOGE("Can't find getObjectProperty");
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index 3cdf48a..0f3e245 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -218,7 +218,7 @@
            return mIEffect->disable();
         }
     }
-    return INVALID_OPERATION;
+    return NO_ERROR;
 }
 
 status_t AudioEffect::command(uint32_t cmdCode,
@@ -231,7 +231,22 @@
         return INVALID_OPERATION;
     }
 
-    return mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData);
+    status_t status = mIEffect->command(cmdCode, cmdSize, cmdData, replySize, replyData);
+    if (status != NO_ERROR) {
+        return status;
+    }
+    status = *(status_t *)replyData;
+    if (status != NO_ERROR) {
+        return status;
+    }
+
+    if (cmdCode == EFFECT_CMD_ENABLE) {
+        android_atomic_or(1, &mEnabled);
+    }
+    if (cmdCode == EFFECT_CMD_DISABLE) {
+        android_atomic_and(~1, &mEnabled);
+    }
+    return status;
 }
 
 
@@ -347,7 +362,11 @@
 {
     LOGV("enableStatusChanged %p enabled %d mCbf %p", this, enabled, mCbf);
     if (mStatus == ALREADY_EXISTS) {
-        mEnabled = enabled;
+        if (enabled) {
+            android_atomic_or(1, &mEnabled);
+        } else {
+            android_atomic_and(~1, &mEnabled);
+        }
         if (mCbf) {
             mCbf(EVENT_ENABLE_STATUS_CHANGED, mUserData, &enabled);
         }
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 77a1476..404762f 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -81,6 +81,7 @@
         libstagefright_httplive \
         libstagefright_rtsp \
         libstagefright_id3 \
+        libstagefright_g711dec \
 
 LOCAL_SHARED_LIBRARIES += \
         libstagefright_amrnb_common \
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index e426fca..efdad43 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -854,18 +854,6 @@
 
 status_t AwesomePlayer::initVideoDecoder() {
     uint32_t flags = 0;
-#if 0
-    if (mRTPSession != NULL) {
-        // XXX hack.
-
-        const char *mime;
-        CHECK(mVideoTrack->getFormat()->findCString(kKeyMIMEType, &mime));
-        if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
-            flags |= OMXCodec::kPreferSoftwareCodecs;
-        }
-    }
-#endif
-
     mVideoSource = OMXCodec::Create(
             mClient.interface(), mVideoTrack->getFormat(),
             false, // createEncoder
@@ -1019,6 +1007,12 @@
 
     int64_t latenessUs = nowUs - timeUs;
 
+    if (mRTPSession != NULL) {
+        // We'll completely ignore timestamps for gtalk videochat
+        // and we'll play incoming video as fast as we get it.
+        latenessUs = 0;
+    }
+
     if (latenessUs > 40000) {
         // We're more than 40ms late.
         LOGV("we're late by %lld us (%.2f secs)", latenessUs, latenessUs / 1E6);
diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp
index 23d8f56..1fd256a 100644
--- a/media/libstagefright/CameraSourceTimeLapse.cpp
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -149,10 +149,10 @@
             LOGV("threadTimeLapseEntry: taking picture");
             CHECK_EQ(OK, mCamera->takePicture());
             mCameraIdle = false;
-            sleep(mTimeBetweenTimeLapseFrameCaptureUs/1E6);
+            usleep(mTimeBetweenTimeLapseFrameCaptureUs);
         } else {
             LOGV("threadTimeLapseEntry: camera busy with old takePicture. Sleeping a little.");
-            sleep(.01);
+            usleep(1E4);
         }
     }
 }
diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp
index 9c99866..ccc6a34 100644
--- a/media/libstagefright/HTTPStream.cpp
+++ b/media/libstagefright/HTTPStream.cpp
@@ -68,7 +68,7 @@
         return UNKNOWN_ERROR;
     }
 
-    setReceiveTimeout(5);  // Time out reads after 5 secs by default
+    setReceiveTimeout(30);  // Time out reads after 30 secs by default
 
     mState = CONNECTING;
 
@@ -158,7 +158,7 @@
 // The workaround accepts both behaviours but could potentially break
 // legitimate responses that use a single newline to "fold" headers, which is
 // why it's not yet on by default.
-#define WORKAROUND_FOR_MISSING_CR       0
+#define WORKAROUND_FOR_MISSING_CR       1
 
 status_t HTTPStream::receive_line(char *line, size_t size) {
     if (mState != CONNECTED) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 0d8c3c6..c860c5c 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -38,6 +38,9 @@
 
 namespace android {
 
+static const uint8_t kNalUnitTypeSeqParamSet = 0x07;
+static const uint8_t kNalUnitTypePicParamSet = 0x08;
+
 class MPEG4Writer::Track {
 public:
     Track(MPEG4Writer *owner, const sp<MediaSource> &source);
@@ -111,6 +114,20 @@
     };
     List<SttsTableEntry> mSttsTableEntries;
 
+    // Sequence parameter set or picture parameter set
+    struct AVCParamSet {
+        AVCParamSet(uint16_t length, const uint8_t *data)
+            : mLength(length), mData(data) {}
+
+        uint16_t mLength;
+        const uint8_t *mData;
+    };
+    List<AVCParamSet> mSeqParamSets;
+    List<AVCParamSet> mPicParamSets;
+    uint8_t mProfileIdc;
+    uint8_t mProfileCompatible;
+    uint8_t mLevelIdc;
+
     void *mCodecSpecificData;
     size_t mCodecSpecificDataSize;
     bool mGotAllCodecSpecificData;
@@ -124,8 +141,15 @@
     static void *ThreadWrapper(void *me);
     void threadEntry();
 
+    const uint8_t *parseParamSet(
+        const uint8_t *data, size_t length, int type, size_t *paramSetLen);
+
     status_t makeAVCCodecSpecificData(
             const uint8_t *data, size_t size);
+    status_t copyAVCCodecSpecificData(
+            const uint8_t *data, size_t size);
+    status_t parseAVCCodecSpecificData(
+            const uint8_t *data, size_t size);
 
     // Track authoring progress status
     void trackProgressStatus(int64_t timeUs, status_t err = OK);
@@ -1038,6 +1062,174 @@
     }
 }
 
+static void getNalUnitType(uint8_t byte, uint8_t* type) {
+    LOGV("getNalUnitType: %d", byte);
+
+    // nal_unit_type: 5-bit unsigned integer
+    *type = (byte & 0x1F);
+}
+
+static const uint8_t *findNextStartCode(
+        const uint8_t *data, size_t length) {
+
+    LOGV("findNextStartCode: %p %d", data, length);
+
+    size_t bytesLeft = length;
+    while (bytesLeft > 4  &&
+            memcmp("\x00\x00\x00\x01", &data[length - bytesLeft], 4)) {
+        --bytesLeft;
+    }
+    if (bytesLeft <= 4) {
+        bytesLeft = 0; // Last parameter set
+    }
+    return &data[length - bytesLeft];
+}
+
+const uint8_t *MPEG4Writer::Track::parseParamSet(
+        const uint8_t *data, size_t length, int type, size_t *paramSetLen) {
+
+    LOGV("parseParamSet");
+    CHECK(type == kNalUnitTypeSeqParamSet ||
+          type == kNalUnitTypePicParamSet);
+
+    const uint8_t *nextStartCode = findNextStartCode(data, length);
+    *paramSetLen = nextStartCode - data;
+    if (*paramSetLen == 0) {
+        LOGE("Param set is malformed, since its length is 0");
+        return NULL;
+    }
+
+    AVCParamSet paramSet(*paramSetLen, data);
+    if (type == kNalUnitTypeSeqParamSet) {
+        if (*paramSetLen < 4) {
+            LOGE("Seq parameter set malformed");
+            return NULL;
+        }
+        if (mSeqParamSets.empty()) {
+            mProfileIdc = data[1];
+            mProfileCompatible = data[2];
+            mLevelIdc = data[3];
+        } else {
+            if (mProfileIdc != data[1] ||
+                mProfileCompatible != data[2] ||
+                mLevelIdc != data[3]) {
+                LOGE("Inconsistent profile/level found in seq parameter sets");
+                return NULL;
+            }
+        }
+        mSeqParamSets.push_back(paramSet);
+    } else {
+        mPicParamSets.push_back(paramSet);
+    }
+    return nextStartCode;
+}
+
+status_t MPEG4Writer::Track::copyAVCCodecSpecificData(
+        const uint8_t *data, size_t size) {
+    LOGV("copyAVCCodecSpecificData");
+
+    // 2 bytes for each of the parameter set length field
+    // plus the 7 bytes for the header
+    if (size < 4 + 7) {
+        LOGE("Codec specific data length too short: %d", size);
+        return ERROR_MALFORMED;
+    }
+
+    mCodecSpecificDataSize = size;
+    mCodecSpecificData = malloc(size);
+    memcpy(mCodecSpecificData, data, size);
+    return OK;
+}
+
+status_t MPEG4Writer::Track::parseAVCCodecSpecificData(
+        const uint8_t *data, size_t size) {
+
+    LOGV("parseAVCCodecSpecificData");
+    // Data starts with a start code.
+    // SPS and PPS are separated with start codes.
+    // Also, SPS must come before PPS
+    uint8_t type = kNalUnitTypeSeqParamSet;
+    bool gotSps = false;
+    bool gotPps = false;
+    const uint8_t *tmp = data;
+    const uint8_t *nextStartCode = data;
+    size_t bytesLeft = size;
+    size_t paramSetLen = 0;
+    mCodecSpecificDataSize = 0;
+    while (bytesLeft > 4 && !memcmp("\x00\x00\x00\x01", tmp, 4)) {
+        getNalUnitType(*(tmp + 4), &type);
+        if (type == kNalUnitTypeSeqParamSet) {
+            if (gotPps) {
+                LOGE("SPS must come before PPS");
+                return ERROR_MALFORMED;
+            }
+            if (!gotSps) {
+                gotSps = true;
+            }
+            nextStartCode = parseParamSet(tmp + 4, bytesLeft - 4, type, &paramSetLen);
+        } else if (type == kNalUnitTypePicParamSet) {
+            if (!gotSps) {
+                LOGE("SPS must come before PPS");
+                return ERROR_MALFORMED;
+            }
+            if (!gotPps) {
+                gotPps = true;
+            }
+            nextStartCode = parseParamSet(tmp + 4, bytesLeft - 4, type, &paramSetLen);
+        } else {
+            LOGE("Only SPS and PPS Nal units are expected");
+            return ERROR_MALFORMED;
+        }
+
+        if (nextStartCode == NULL) {
+            return ERROR_MALFORMED;
+        }
+
+        // Move on to find the next parameter set
+        bytesLeft -= nextStartCode - tmp;
+        tmp = nextStartCode;
+        mCodecSpecificDataSize += (2 + paramSetLen);
+    }
+
+    {
+        // Check on the number of seq parameter sets
+        size_t nSeqParamSets = mSeqParamSets.size();
+        if (nSeqParamSets == 0) {
+            LOGE("Cound not find sequence parameter set");
+            return ERROR_MALFORMED;
+        }
+
+        if (nSeqParamSets > 0x1F) {
+            LOGE("Too many seq parameter sets (%d) found", nSeqParamSets);
+            return ERROR_MALFORMED;
+        }
+    }
+
+    {
+        // Check on the number of pic parameter sets
+        size_t nPicParamSets = mPicParamSets.size();
+        if (nPicParamSets == 0) {
+            LOGE("Cound not find picture parameter set");
+            return ERROR_MALFORMED;
+        }
+        if (nPicParamSets > 0xFF) {
+            LOGE("Too many pic parameter sets (%d) found", nPicParamSets);
+            return ERROR_MALFORMED;
+        }
+    }
+
+    {
+        // Check on the profiles
+        // These profiles requires additional parameter set extensions
+        if (mProfileIdc == 100 || mProfileIdc == 110 ||
+            mProfileIdc == 122 || mProfileIdc == 144) {
+            LOGE("Sorry, no support for profile_idc: %d!", mProfileIdc);
+            return BAD_VALUE;
+        }
+    }
+
+    return OK;
+}
 
 status_t MPEG4Writer::Track::makeAVCCodecSpecificData(
         const uint8_t *data, size_t size) {
@@ -1048,50 +1240,67 @@
         return ERROR_MALFORMED;
     }
 
-    if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) {
-        LOGE("Must start with a start code");
+    if (size < 4) {
+        LOGE("Codec specific data length too short: %d", size);
         return ERROR_MALFORMED;
     }
 
-    size_t picParamOffset = 4;
-    while (picParamOffset + 3 < size
-            && memcmp("\x00\x00\x00\x01", &data[picParamOffset], 4)) {
-        ++picParamOffset;
+    // Data is in the form of AVCCodecSpecificData
+    if (memcmp("\x00\x00\x00\x01", data, 4)) {
+        return copyAVCCodecSpecificData(data, size);
     }
 
-    if (picParamOffset + 3 >= size) {
-        LOGE("Could not find start-code for pictureParameterSet");
+    if (parseAVCCodecSpecificData(data, size) != OK) {
         return ERROR_MALFORMED;
     }
 
-    size_t seqParamSetLength = picParamOffset - 4;
-    size_t picParamSetLength = size - picParamOffset - 4;
-
-    mCodecSpecificDataSize =
-        6 + 1 + seqParamSetLength + 2 + picParamSetLength + 2;
-
+    // ISO 14496-15: AVC file format
+    mCodecSpecificDataSize += 7;  // 7 more bytes in the header
     mCodecSpecificData = malloc(mCodecSpecificDataSize);
     uint8_t *header = (uint8_t *)mCodecSpecificData;
-    header[0] = 1;
-    header[1] = 0x42;  // profile
-    header[2] = 0x80;
-    header[3] = 0x1e;  // level
+    header[0] = 1;                     // version
+    header[1] = mProfileIdc;           // profile indication
+    header[2] = mProfileCompatible;    // profile compatibility
+    header[3] = mLevelIdc;
 
+    // 6-bit '111111' followed by 2-bit to lengthSizeMinuusOne
 #if USE_NALLEN_FOUR
     header[4] = 0xfc | 3;  // length size == 4 bytes
 #else
     header[4] = 0xfc | 1;  // length size == 2 bytes
 #endif
 
-    header[5] = 0xe0 | 1;
-    header[6] = seqParamSetLength >> 8;
-    header[7] = seqParamSetLength & 0xff;
-    memcpy(&header[8], &data[4], seqParamSetLength);
-    header += 8 + seqParamSetLength;
-    header[0] = 1;
-    header[1] = picParamSetLength >> 8;
-    header[2] = picParamSetLength & 0xff;
-    memcpy(&header[3], &data[picParamOffset + 4], picParamSetLength);
+    // 3-bit '111' followed by 5-bit numSequenceParameterSets
+    int nSequenceParamSets = mSeqParamSets.size();
+    header[5] = 0xe0 | nSequenceParamSets;
+    header += 6;
+    for (List<AVCParamSet>::iterator it = mSeqParamSets.begin();
+         it != mSeqParamSets.end(); ++it) {
+        // 16-bit sequence parameter set length
+        uint16_t seqParamSetLength = it->mLength;
+        header[0] = seqParamSetLength >> 8;
+        header[1] = seqParamSetLength & 0xff;
+
+        // SPS NAL unit (sequence parameter length bytes)
+        memcpy(&header[2], it->mData, seqParamSetLength);
+        header += (2 + seqParamSetLength);
+    }
+
+    // 8-bit nPictureParameterSets
+    int nPictureParamSets = mPicParamSets.size();
+    header[0] = nPictureParamSets;
+    header += 1;
+    for (List<AVCParamSet>::iterator it = mPicParamSets.begin();
+         it != mPicParamSets.end(); ++it) {
+        // 16-bit picture parameter set length
+        uint16_t picParamSetLength = it->mLength;
+        header[0] = picParamSetLength >> 8;
+        header[1] = picParamSetLength & 0xff;
+
+        // PPS Nal unit (picture parameter set length bytes)
+        memcpy(&header[2], it->mData, picParamSetLength);
+        header += (2 + picParamSetLength);
+    }
 
     return OK;
 }
@@ -1168,91 +1377,6 @@
 
             mGotAllCodecSpecificData = true;
             continue;
-        } else if (!mGotAllCodecSpecificData &&
-                count == 1 && mIsMPEG4 && mCodecSpecificData == NULL) {
-            // The TI mpeg4 encoder does not properly set the
-            // codec-specific-data flag.
-
-            const uint8_t *data =
-                (const uint8_t *)buffer->data() + buffer->range_offset();
-
-            const size_t size = buffer->range_length();
-
-            size_t offset = 0;
-            while (offset + 3 < size) {
-                if (data[offset] == 0x00 && data[offset + 1] == 0x00
-                    && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) {
-                    break;
-                }
-
-                ++offset;
-            }
-
-            // CHECK(offset + 3 < size);
-            if (offset + 3 >= size) {
-                // XXX assume the entire first chunk of data is the codec specific
-                // data.
-                offset = size;
-            }
-
-            mCodecSpecificDataSize = offset;
-            mCodecSpecificData = malloc(offset);
-            memcpy(mCodecSpecificData, data, offset);
-
-            buffer->set_range(buffer->range_offset() + offset, size - offset);
-
-            if (size == offset) {
-                buffer->release();
-                buffer = NULL;
-
-                continue;
-            }
-
-            mGotAllCodecSpecificData = true;
-        } else if (!mGotAllCodecSpecificData && mIsAvc && count < 3) {
-            // The TI video encoder does not flag codec specific data
-            // as such and also splits up SPS and PPS across two buffers.
-
-            const uint8_t *data =
-                (const uint8_t *)buffer->data() + buffer->range_offset();
-
-            size_t size = buffer->range_length();
-
-            CHECK(count == 2 || mCodecSpecificData == NULL);
-
-            size_t offset = mCodecSpecificDataSize;
-            mCodecSpecificDataSize += size + 4;
-            mCodecSpecificData =
-                realloc(mCodecSpecificData, mCodecSpecificDataSize);
-
-            memcpy((uint8_t *)mCodecSpecificData + offset,
-                   "\x00\x00\x00\x01", 4);
-
-            memcpy((uint8_t *)mCodecSpecificData + offset + 4, data, size);
-
-            buffer->release();
-            buffer = NULL;
-
-            if (count == 2) {
-                void *tmp = mCodecSpecificData;
-                size = mCodecSpecificDataSize;
-                mCodecSpecificData = NULL;
-                mCodecSpecificDataSize = 0;
-
-                status_t err = makeAVCCodecSpecificData(
-                        (const uint8_t *)tmp, size);
-                free(tmp);
-                tmp = NULL;
-                CHECK_EQ(OK, err);
-
-                mGotAllCodecSpecificData = true;
-            }
-
-            continue;
-        }
-
-        if (!mGotAllCodecSpecificData) {
-            mGotAllCodecSpecificData = true;
         }
 
         // Make a deep copy of the MediaBuffer and Metadata and release
@@ -1753,6 +1877,8 @@
                   mOwner->writeInt32(samplerate << 16);
                   if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AAC, mime)) {
                     mOwner->beginBox("esds");
+                        CHECK(mCodecSpecificData);
+                        CHECK(mCodecSpecificDataSize > 0);
 
                         mOwner->writeInt32(0);     // version=0, flags=0
                         mOwner->writeInt8(0x03);   // ES_DescrTag
@@ -1830,6 +1956,8 @@
                   mOwner->writeInt16(0x18);        // depth
                   mOwner->writeInt16(-1);          // predefined
 
+                  CHECK(mCodecSpecificData);
+                  CHECK(mCodecSpecificDataSize > 0);
                   CHECK(23 + mCodecSpecificDataSize < 128);
 
                   if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_MPEG4, mime)) {
@@ -1877,6 +2005,8 @@
 
                       mOwner->endBox();  // d263
                   } else if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime)) {
+                      CHECK(mCodecSpecificData);
+                      CHECK(mCodecSpecificDataSize > 0);
                       mOwner->beginBox("avcC");
                         mOwner->write(mCodecSpecificData, mCodecSpecificDataSize);
                       mOwner->endBox();  // avcC
diff --git a/media/libstagefright/MediaDefs.cpp b/media/libstagefright/MediaDefs.cpp
index 39d264c..7648d42 100644
--- a/media/libstagefright/MediaDefs.cpp
+++ b/media/libstagefright/MediaDefs.cpp
@@ -32,6 +32,8 @@
 const char *MEDIA_MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
 const char *MEDIA_MIMETYPE_AUDIO_QCELP = "audio/qcelp";
 const char *MEDIA_MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
+const char *MEDIA_MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
+const char *MEDIA_MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
 const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw";
 
 const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 4f3bffd..4741b1d 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -26,6 +26,7 @@
 #include "include/AMRWBEncoder.h"
 #include "include/AVCDecoder.h"
 #include "include/AVCEncoder.h"
+#include "include/G711Decoder.h"
 #include "include/M4vH263Decoder.h"
 #include "include/M4vH263Encoder.h"
 #include "include/MP3Decoder.h"
@@ -77,6 +78,7 @@
 FACTORY_CREATE(AMRWBDecoder)
 FACTORY_CREATE(AACDecoder)
 FACTORY_CREATE(AVCDecoder)
+FACTORY_CREATE(G711Decoder)
 FACTORY_CREATE(M4vH263Decoder)
 FACTORY_CREATE(VorbisDecoder)
 FACTORY_CREATE(VPXDecoder)
@@ -124,6 +126,7 @@
         FACTORY_REF(AMRWBDecoder)
         FACTORY_REF(AACDecoder)
         FACTORY_REF(AVCDecoder)
+        FACTORY_REF(G711Decoder)
         FACTORY_REF(M4vH263Decoder)
         FACTORY_REF(VorbisDecoder)
         FACTORY_REF(VPXDecoder)
@@ -155,6 +158,8 @@
     { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.TI.AAC.decode" },
     { MEDIA_MIMETYPE_AUDIO_AAC, "AACDecoder" },
 //    { MEDIA_MIMETYPE_AUDIO_AAC, "OMX.PV.aacdec" },
+    { MEDIA_MIMETYPE_AUDIO_G711_ALAW, "G711Decoder" },
+    { MEDIA_MIMETYPE_AUDIO_G711_MLAW, "G711Decoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.Decoder" },
@@ -1680,6 +1685,14 @@
 
                 MediaBuffer *buffer = info->mMediaBuffer;
 
+                if (msg.u.extended_buffer_data.range_offset
+                        + msg.u.extended_buffer_data.range_length
+                            > buffer->size()) {
+                    CODEC_LOGE(
+                            "Codec lied about its buffer size requirements, "
+                            "sending a buffer larger than the originally "
+                            "advertised size in FILL_BUFFER_DONE!");
+                }
                 buffer->set_range(
                         msg.u.extended_buffer_data.range_offset,
                         msg.u.extended_buffer_data.range_length);
diff --git a/media/libstagefright/WAVExtractor.cpp b/media/libstagefright/WAVExtractor.cpp
index 39b1b96..8d820c0 100644
--- a/media/libstagefright/WAVExtractor.cpp
+++ b/media/libstagefright/WAVExtractor.cpp
@@ -31,7 +31,11 @@
 
 namespace android {
 
-static uint16_t WAVE_FORMAT_PCM = 1;
+enum {
+    WAVE_FORMAT_PCM = 1,
+    WAVE_FORMAT_ALAW = 6,
+    WAVE_FORMAT_MULAW = 7,
+};
 
 static uint32_t U32_LE_AT(const uint8_t *ptr) {
     return ptr[3] << 24 | ptr[2] << 16 | ptr[1] << 8 | ptr[0];
@@ -45,6 +49,7 @@
     WAVSource(
             const sp<DataSource> &dataSource,
             const sp<MetaData> &meta,
+            uint16_t waveFormat,
             int32_t bitsPerSample,
             off_t offset, size_t size);
 
@@ -63,6 +68,7 @@
 
     sp<DataSource> mDataSource;
     sp<MetaData> mMeta;
+    uint16_t mWaveFormat;
     int32_t mSampleRate;
     int32_t mNumChannels;
     int32_t mBitsPerSample;
@@ -108,7 +114,7 @@
 
     return new WAVSource(
             mDataSource, mTrackMeta,
-            mBitsPerSample, mDataOffset, mDataSize);
+            mWaveFormat, mBitsPerSample, mDataOffset, mDataSize);
 }
 
 sp<MetaData> WAVExtractor::getTrackMetaData(
@@ -160,8 +166,10 @@
                 return NO_INIT;
             }
 
-            uint16_t format = U16_LE_AT(formatSpec);
-            if (format != WAVE_FORMAT_PCM) {
+            mWaveFormat = U16_LE_AT(formatSpec);
+            if (mWaveFormat != WAVE_FORMAT_PCM
+                    && mWaveFormat != WAVE_FORMAT_ALAW
+                    && mWaveFormat != WAVE_FORMAT_MULAW) {
                 return ERROR_UNSUPPORTED;
             }
 
@@ -178,9 +186,17 @@
 
             mBitsPerSample = U16_LE_AT(&formatSpec[14]);
 
-            if (mBitsPerSample != 8 && mBitsPerSample != 16
-                && mBitsPerSample != 24) {
-                return ERROR_UNSUPPORTED;
+            if (mWaveFormat == WAVE_FORMAT_PCM) {
+                if (mBitsPerSample != 8 && mBitsPerSample != 16
+                    && mBitsPerSample != 24) {
+                    return ERROR_UNSUPPORTED;
+                }
+            } else {
+                CHECK(mWaveFormat == WAVE_FORMAT_MULAW
+                        || mWaveFormat == WAVE_FORMAT_ALAW);
+                if (mBitsPerSample != 8) {
+                    return ERROR_UNSUPPORTED;
+                }
             }
 
             mValidFormat = true;
@@ -190,7 +206,23 @@
                 mDataSize = chunkSize;
 
                 mTrackMeta = new MetaData;
-                mTrackMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+
+                switch (mWaveFormat) {
+                    case WAVE_FORMAT_PCM:
+                        mTrackMeta->setCString(
+                                kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+                        break;
+                    case WAVE_FORMAT_ALAW:
+                        mTrackMeta->setCString(
+                                kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_ALAW);
+                        break;
+                    default:
+                        CHECK_EQ(mWaveFormat, WAVE_FORMAT_MULAW);
+                        mTrackMeta->setCString(
+                                kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_MLAW);
+                        break;
+                }
+
                 mTrackMeta->setInt32(kKeyChannelCount, mNumChannels);
                 mTrackMeta->setInt32(kKeySampleRate, mSampleRate);
 
@@ -217,10 +249,12 @@
 WAVSource::WAVSource(
         const sp<DataSource> &dataSource,
         const sp<MetaData> &meta,
+        uint16_t waveFormat,
         int32_t bitsPerSample,
         off_t offset, size_t size)
     : mDataSource(dataSource),
       mMeta(meta),
+      mWaveFormat(waveFormat),
       mSampleRate(0),
       mNumChannels(0),
       mBitsPerSample(bitsPerSample),
@@ -312,43 +346,45 @@
 
     buffer->set_range(0, n);
 
-    if (mBitsPerSample == 8) {
-        // Convert 8-bit unsigned samples to 16-bit signed.
+    if (mWaveFormat == WAVE_FORMAT_PCM) {
+        if (mBitsPerSample == 8) {
+            // Convert 8-bit unsigned samples to 16-bit signed.
 
-        MediaBuffer *tmp;
-        CHECK_EQ(mGroup->acquire_buffer(&tmp), OK);
+            MediaBuffer *tmp;
+            CHECK_EQ(mGroup->acquire_buffer(&tmp), OK);
 
-        // The new buffer holds the sample number of samples, but each
-        // one is 2 bytes wide.
-        tmp->set_range(0, 2 * n);
+            // The new buffer holds the sample number of samples, but each
+            // one is 2 bytes wide.
+            tmp->set_range(0, 2 * n);
 
-        int16_t *dst = (int16_t *)tmp->data();
-        const uint8_t *src = (const uint8_t *)buffer->data();
-        while (n-- > 0) {
-            *dst++ = ((int16_t)(*src) - 128) * 256;
-            ++src;
+            int16_t *dst = (int16_t *)tmp->data();
+            const uint8_t *src = (const uint8_t *)buffer->data();
+            while (n-- > 0) {
+                *dst++ = ((int16_t)(*src) - 128) * 256;
+                ++src;
+            }
+
+            buffer->release();
+            buffer = tmp;
+        } else if (mBitsPerSample == 24) {
+            // Convert 24-bit signed samples to 16-bit signed.
+
+            const uint8_t *src =
+                (const uint8_t *)buffer->data() + buffer->range_offset();
+            int16_t *dst = (int16_t *)src;
+
+            size_t numSamples = buffer->range_length() / 3;
+            for (size_t i = 0; i < numSamples; ++i) {
+                int32_t x = (int32_t)(src[0] | src[1] << 8 | src[2] << 16);
+                x = (x << 8) >> 8;  // sign extension
+
+                x = x >> 8;
+                *dst++ = (int16_t)x;
+                src += 3;
+            }
+
+            buffer->set_range(buffer->range_offset(), 2 * numSamples);
         }
-
-        buffer->release();
-        buffer = tmp;
-    } else if (mBitsPerSample == 24) {
-        // Convert 24-bit signed samples to 16-bit signed.
-
-        const uint8_t *src =
-            (const uint8_t *)buffer->data() + buffer->range_offset();
-        int16_t *dst = (int16_t *)src;
-
-        size_t numSamples = buffer->range_length() / 3;
-        for (size_t i = 0; i < numSamples; ++i) {
-            int32_t x = (int32_t)(src[0] | src[1] << 8 | src[2] << 16);
-            x = (x << 8) >> 8;  // sign extension
-
-            x = x >> 8;
-            *dst++ = (int16_t)x;
-            src += 3;
-        }
-
-        buffer->set_range(buffer->range_offset(), 2 * numSamples);
     }
 
     size_t bytesPerSample = mBitsPerSample >> 3;
diff --git a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp
index d5eb156..6e74279 100644
--- a/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp
+++ b/media/libstagefright/codecs/avc/enc/AVCEncoder.cpp
@@ -337,14 +337,18 @@
 
     MediaBuffer *outputBuffer;
     CHECK_EQ(OK, mGroup->acquire_buffer(&outputBuffer));
-    uint8_t *outPtr = (uint8_t *) outputBuffer->data();
-    uint32_t dataLength = outputBuffer->size();
+
+    // Add 4 bytes for the start code 0x00000001
+    uint8_t *outPtr = (uint8_t *) outputBuffer->data() + 4;
+    uint32_t dataLength = outputBuffer->size() - 4;
 
     int32_t type;
     AVCEnc_Status encoderStatus = AVCENC_SUCCESS;
 
-    // Return SPS and PPS for the first two buffers
-    if (!mSpsPpsHeaderReceived) {
+    // Combine SPS and PPS and place them in the very first output buffer
+    // SPS and PPS are separated by start code 0x00000001
+    // Assume that we have exactly one SPS and exactly one PPS.
+    while (!mSpsPpsHeaderReceived && mNumInputFrames <= 0) {
         encoderStatus = PVAVCEncodeNAL(mHandle, outPtr, &dataLength, &type);
         if (encoderStatus == AVCENC_WRONG_STATE) {
             mSpsPpsHeaderReceived = true;
@@ -352,11 +356,22 @@
         } else {
             switch (type) {
                 case AVC_NALTYPE_SPS:
-                case AVC_NALTYPE_PPS:
-                    LOGV("%s received",
-                            (type == AVC_NALTYPE_SPS)? "SPS": "PPS");
                     ++mNumInputFrames;
-                    outputBuffer->set_range(0, dataLength);
+                    memcpy(outputBuffer->data(), "\x00\x00\x00\x01", 4);
+                    outputBuffer->set_range(0, dataLength + 4);
+                    outPtr += (dataLength + 4);  // 4 bytes for next start code
+                    dataLength = outputBuffer->size() -
+                            (outputBuffer->range_length() + 4);
+                    break;
+                case AVC_NALTYPE_PPS:
+                    ++mNumInputFrames;
+                    memcpy(((uint8_t *) outputBuffer->data()) +
+                            outputBuffer->range_length(),
+                            "\x00\x00\x00\x01", 4);
+                    outputBuffer->set_range(0,
+                            dataLength + outputBuffer->range_length() + 4);
+                    outputBuffer->meta_data()->setInt32(kKeyIsCodecConfig, 1);
+                    outputBuffer->meta_data()->setInt64(kKeyTime, 0);
                     *out = outputBuffer;
                     return OK;
                 default:
@@ -376,8 +391,18 @@
         if (err != OK) {
             LOGE("Failed to read input video frame: %d", err);
             outputBuffer->release();
+            mInputBuffer->release();
+            mInputBuffer = NULL;
             return err;
         }
+
+        if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) {
+            outputBuffer->release();
+            mInputBuffer->release();
+            mInputBuffer = NULL;
+            return UNKNOWN_ERROR;
+        }
+
         int64_t timeUs;
         CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
         outputBuffer->meta_data()->setInt64(kKeyTime, timeUs);
diff --git a/media/libstagefright/codecs/g711/Android.mk b/media/libstagefright/codecs/g711/Android.mk
new file mode 100644
index 0000000..2e431205
--- /dev/null
+++ b/media/libstagefright/codecs/g711/Android.mk
@@ -0,0 +1,4 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/libstagefright/codecs/g711/dec/Android.mk b/media/libstagefright/codecs/g711/dec/Android.mk
new file mode 100644
index 0000000..cfb9fe4
--- /dev/null
+++ b/media/libstagefright/codecs/g711/dec/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    G711Decoder.cpp
+
+LOCAL_C_INCLUDES := \
+        frameworks/base/media/libstagefright/include \
+
+LOCAL_MODULE := libstagefright_g711dec
+
+include $(BUILD_STATIC_LIBRARY)
diff --git a/media/libstagefright/codecs/g711/dec/G711Decoder.cpp b/media/libstagefright/codecs/g711/dec/G711Decoder.cpp
new file mode 100644
index 0000000..4414e4e
--- /dev/null
+++ b/media/libstagefright/codecs/g711/dec/G711Decoder.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "G711Decoder"
+#include <utils/Log.h>
+
+#include "G711Decoder.h"
+
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+
+static const size_t kMaxNumSamplesPerFrame = 16384;
+
+namespace android {
+
+G711Decoder::G711Decoder(const sp<MediaSource> &source)
+    : mSource(source),
+      mStarted(false),
+      mBufferGroup(NULL) {
+}
+
+G711Decoder::~G711Decoder() {
+    if (mStarted) {
+        stop();
+    }
+}
+
+status_t G711Decoder::start(MetaData *params) {
+    CHECK(!mStarted);
+
+    const char *mime;
+    CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+    mIsMLaw = false;
+    if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_MLAW)) {
+        mIsMLaw = true;
+    } else if (strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_G711_ALAW)) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    mBufferGroup = new MediaBufferGroup;
+    mBufferGroup->add_buffer(
+            new MediaBuffer(kMaxNumSamplesPerFrame * sizeof(int16_t)));
+
+    mSource->start();
+
+    mStarted = true;
+
+    return OK;
+}
+
+status_t G711Decoder::stop() {
+    CHECK(mStarted);
+
+    delete mBufferGroup;
+    mBufferGroup = NULL;
+
+    mSource->stop();
+
+    mStarted = false;
+
+    return OK;
+}
+
+sp<MetaData> G711Decoder::getFormat() {
+    sp<MetaData> srcFormat = mSource->getFormat();
+
+    int32_t numChannels;
+    int32_t sampleRate;
+
+    CHECK(srcFormat->findInt32(kKeyChannelCount, &numChannels));
+    CHECK(srcFormat->findInt32(kKeySampleRate, &sampleRate));
+
+    sp<MetaData> meta = new MetaData;
+    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
+    meta->setInt32(kKeyChannelCount, numChannels);
+    meta->setInt32(kKeySampleRate, sampleRate);
+
+    int64_t durationUs;
+    if (srcFormat->findInt64(kKeyDuration, &durationUs)) {
+        meta->setInt64(kKeyDuration, durationUs);
+    }
+
+    meta->setCString(kKeyDecoderComponent, "G711Decoder");
+
+    return meta;
+}
+
+status_t G711Decoder::read(
+        MediaBuffer **out, const ReadOptions *options) {
+    status_t err;
+
+    *out = NULL;
+
+    int64_t seekTimeUs;
+    ReadOptions::SeekMode mode;
+    if (options && options->getSeekTo(&seekTimeUs, &mode)) {
+        CHECK(seekTimeUs >= 0);
+    } else {
+        seekTimeUs = -1;
+    }
+
+    MediaBuffer *inBuffer;
+    err = mSource->read(&inBuffer, options);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (inBuffer->range_length() > kMaxNumSamplesPerFrame) {
+        LOGE("input buffer too large (%d).", inBuffer->range_length());
+
+        inBuffer->release();
+        inBuffer = NULL;
+
+        return ERROR_UNSUPPORTED;
+    }
+
+    int64_t timeUs;
+    CHECK(inBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
+
+    const uint8_t *inputPtr =
+        (const uint8_t *)inBuffer->data() + inBuffer->range_offset();
+
+    MediaBuffer *outBuffer;
+    CHECK_EQ(mBufferGroup->acquire_buffer(&outBuffer), OK);
+
+    if (mIsMLaw) {
+        DecodeMLaw(
+                static_cast<int16_t *>(outBuffer->data()),
+                inputPtr, inBuffer->range_length());
+    } else {
+        DecodeALaw(
+                static_cast<int16_t *>(outBuffer->data()),
+                inputPtr, inBuffer->range_length());
+    }
+
+    // Each 8-bit byte is converted into a 16-bit sample.
+    outBuffer->set_range(0, inBuffer->range_length() * 2);
+
+    outBuffer->meta_data()->setInt64(kKeyTime, timeUs);
+
+    inBuffer->release();
+    inBuffer = NULL;
+
+    *out = outBuffer;
+
+    return OK;
+}
+
+// static
+void G711Decoder::DecodeALaw(
+        int16_t *out, const uint8_t *in, size_t inSize) {
+    while (inSize-- > 0) {
+        int32_t x = *in++;
+
+        int32_t ix = x ^ 0x55;
+        ix &= 0x7f;
+
+        int32_t iexp = ix >> 4;
+        int32_t mant = ix & 0x0f;
+
+        if (iexp > 0) {
+            mant += 16;
+        }
+
+        mant = (mant << 4) + 8;
+
+        if (iexp > 1) {
+            mant = mant << (iexp - 1);
+        }
+
+        *out++ = (x > 127) ? mant : -mant;
+    }
+}
+
+// static
+void G711Decoder::DecodeMLaw(
+        int16_t *out, const uint8_t *in, size_t inSize) {
+    while (inSize-- > 0) {
+        int32_t x = *in++;
+
+        int32_t mantissa = ~x;
+        int32_t exponent = (mantissa >> 4) & 7;
+        int32_t segment = exponent + 1;
+        mantissa &= 0x0f;
+
+        int32_t step = 4 << segment;
+
+        int32_t abs = (0x80l << exponent) + step * mantissa + step / 2 - 4 * 33;
+
+        *out++ = (x < 0x80) ? -abs : abs;
+    }
+}
+
+}  // namespace android
diff --git a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp
index 5002442e..1bef0e9 100644
--- a/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp
+++ b/media/libstagefright/codecs/m4v_h263/enc/M4vH263Encoder.cpp
@@ -292,8 +292,18 @@
     if (OK != mSource->read(&mInputBuffer, options)) {
         LOGE("Failed to read from data source");
         outputBuffer->release();
+        mInputBuffer->release();
+        mInputBuffer = NULL;
         return UNKNOWN_ERROR;
     }
+
+    if (mInputBuffer->size() - ((mVideoWidth * mVideoHeight * 3) >> 1) != 0) {
+        outputBuffer->release();
+        mInputBuffer->release();
+        mInputBuffer = NULL;
+        return UNKNOWN_ERROR;
+    }
+
     int64_t timeUs;
     CHECK(mInputBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
     if (mNextModTimeUs > timeUs) {
diff --git a/media/libstagefright/include/G711Decoder.h b/media/libstagefright/include/G711Decoder.h
new file mode 100644
index 0000000..8b5143a
--- /dev/null
+++ b/media/libstagefright/include/G711Decoder.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef G711_DECODER_H_
+
+#define G711_DECODER_H_
+
+#include <media/stagefright/MediaSource.h>
+
+namespace android {
+
+struct MediaBufferGroup;
+
+struct G711Decoder : public MediaSource {
+    G711Decoder(const sp<MediaSource> &source);
+
+    virtual status_t start(MetaData *params);
+    virtual status_t stop();
+
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options);
+
+protected:
+    virtual ~G711Decoder();
+
+private:
+    sp<MediaSource> mSource;
+    bool mStarted;
+    bool mIsMLaw;
+
+    MediaBufferGroup *mBufferGroup;
+
+    static void DecodeALaw(int16_t *out, const uint8_t *in, size_t inSize);
+    static void DecodeMLaw(int16_t *out, const uint8_t *in, size_t inSize);
+
+    G711Decoder(const G711Decoder &);
+    G711Decoder &operator=(const G711Decoder &);
+};
+
+}  // namespace android
+
+#endif  // G711_DECODER_H_
diff --git a/media/libstagefright/include/WAVExtractor.h b/media/libstagefright/include/WAVExtractor.h
index 9384942..3e847b9 100644
--- a/media/libstagefright/include/WAVExtractor.h
+++ b/media/libstagefright/include/WAVExtractor.h
@@ -43,6 +43,7 @@
     sp<DataSource> mDataSource;
     status_t mInitCheck;
     bool mValidFormat;
+    uint16_t mWaveFormat;
     uint16_t mNumChannels;
     uint32_t mSampleRate;
     uint16_t mBitsPerSample;
diff --git a/media/libstagefright/rtsp/APacketSource.cpp b/media/libstagefright/rtsp/APacketSource.cpp
index a577704..395cd28 100644
--- a/media/libstagefright/rtsp/APacketSource.cpp
+++ b/media/libstagefright/rtsp/APacketSource.cpp
@@ -356,24 +356,10 @@
     if (!mBuffers.empty()) {
         const sp<ABuffer> buffer = *mBuffers.begin();
 
-        uint64_t ntpTime;
-        CHECK(buffer->meta()->findInt64(
-                    "ntp-time", (int64_t *)&ntpTime));
-
         MediaBuffer *mediaBuffer = new MediaBuffer(buffer->size());
-        mediaBuffer->meta_data()->setInt64(kKeyNTPTime, ntpTime);
 
-        if (mFirstAccessUnit) {
-            mFirstAccessUnit = false;
-            mFirstAccessUnitNTP = ntpTime;
-        }
-        if (ntpTime > mFirstAccessUnitNTP) {
-            ntpTime -= mFirstAccessUnitNTP;
-        } else {
-            ntpTime = 0;
-        }
-
-        int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+        int64_t timeUs;
+        CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
 
         mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs);
 
@@ -390,10 +376,29 @@
 void APacketSource::queueAccessUnit(const sp<ABuffer> &buffer) {
     int32_t damaged;
     if (buffer->meta()->findInt32("damaged", &damaged) && damaged) {
-        // LOG(VERBOSE) << "discarding damaged AU";
+        LOG(INFO) << "discarding damaged AU";
         return;
     }
 
+    uint64_t ntpTime;
+    CHECK(buffer->meta()->findInt64(
+                "ntp-time", (int64_t *)&ntpTime));
+
+    if (mFirstAccessUnit) {
+        mFirstAccessUnit = false;
+        mFirstAccessUnitNTP = ntpTime;
+    }
+
+    if (ntpTime > mFirstAccessUnitNTP) {
+        ntpTime -= mFirstAccessUnitNTP;
+    } else {
+        ntpTime = 0;
+    }
+
+    int64_t timeUs = (int64_t)(ntpTime * 1E6 / (1ll << 32));
+
+    buffer->meta()->setInt64("timeUs", timeUs);
+
     Mutex::Autolock autoLock(mLock);
     mBuffers.push_back(buffer);
     mCondition.signal();
diff --git a/media/libstagefright/rtsp/ARTPConnection.cpp b/media/libstagefright/rtsp/ARTPConnection.cpp
index 5bd306b..469af3e 100644
--- a/media/libstagefright/rtsp/ARTPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTPConnection.cpp
@@ -28,8 +28,6 @@
 #include <arpa/inet.h>
 #include <sys/socket.h>
 
-#define IGNORE_RTCP_TIME        0
-
 namespace android {
 
 static const size_t kMaxUDPSize = 1500;
@@ -61,8 +59,9 @@
     struct sockaddr_in mRemoteRTCPAddr;
 };
 
-ARTPConnection::ARTPConnection()
-    : mPollEventPending(false),
+ARTPConnection::ARTPConnection(uint32_t flags)
+    : mFlags(flags),
+      mPollEventPending(false),
       mLastReceiverReportTimeUs(-1) {
 }
 
@@ -280,7 +279,10 @@
                 sp<ARTPSource> source = s->mSources.valueAt(i);
 
                 source->addReceiverReport(buffer);
-                source->addFIR(buffer);
+
+                if (mFlags & kRegularlyRequestFIR) {
+                    source->addFIR(buffer);
+                }
             }
 
             if (buffer->size() > 0) {
@@ -405,13 +407,11 @@
     buffer->setInt32Data(u16at(&data[2]));
     buffer->setRange(payloadOffset, size - payloadOffset);
 
-#if IGNORE_RTCP_TIME
-    if (!source->timeEstablished()) {
+    if ((mFlags & kFakeTimestamps) && !source->timeEstablished()) {
         source->timeUpdate(rtpTime, 0);
-        source->timeUpdate(rtpTime + 20, 0x100000000ll);
+        source->timeUpdate(rtpTime + 90000, 0x100000000ll);
         CHECK(source->timeEstablished());
     }
-#endif
 
     source->processRTPPacket(buffer);
 
@@ -533,9 +533,9 @@
 
     sp<ARTPSource> source = findSource(s, id);
 
-#if !IGNORE_RTCP_TIME
-    source->timeUpdate(rtpTime, ntpTime);
-#endif
+    if ((mFlags & kFakeTimestamps) == 0) {
+        source->timeUpdate(rtpTime, ntpTime);
+    }
 
     return 0;
 }
diff --git a/media/libstagefright/rtsp/ARTPConnection.h b/media/libstagefright/rtsp/ARTPConnection.h
index 49839ad..c535199 100644
--- a/media/libstagefright/rtsp/ARTPConnection.h
+++ b/media/libstagefright/rtsp/ARTPConnection.h
@@ -28,7 +28,12 @@
 struct ASessionDescription;
 
 struct ARTPConnection : public AHandler {
-    ARTPConnection();
+    enum Flags {
+        kFakeTimestamps      = 1,
+        kRegularlyRequestFIR = 2,
+    };
+
+    ARTPConnection(uint32_t flags = 0);
 
     void addStream(
             int rtpSocket, int rtcpSocket,
@@ -56,6 +61,8 @@
 
     static const int64_t kSelectTimeoutUs;
 
+    uint32_t mFlags;
+
     struct StreamInfo;
     List<StreamInfo> mStreams;
 
diff --git a/media/libstagefright/rtsp/ARTPSession.cpp b/media/libstagefright/rtsp/ARTPSession.cpp
index 0e0f45a..e082078 100644
--- a/media/libstagefright/rtsp/ARTPSession.cpp
+++ b/media/libstagefright/rtsp/ARTPSession.cpp
@@ -40,7 +40,10 @@
 
     mDesc = desc;
 
-    mRTPConn = new ARTPConnection;
+    mRTPConn = new ARTPConnection(
+            ARTPConnection::kFakeTimestamps
+                | ARTPConnection::kRegularlyRequestFIR);
+
     looper()->registerHandler(mRTPConn);
 
     for (size_t i = 1; i < mDesc->countTracks(); ++i) {
diff --git a/media/libstagefright/rtsp/ARTPSource.cpp b/media/libstagefright/rtsp/ARTPSource.cpp
index e08183e..225f6e8 100644
--- a/media/libstagefright/rtsp/ARTPSource.cpp
+++ b/media/libstagefright/rtsp/ARTPSource.cpp
@@ -98,7 +98,7 @@
     mNTPTime[mNumTimes] = ntpTime;
     mRTPTime[mNumTimes++] = rtpTime;
 
-    if (mNumTimes == 2) {
+    if (timeEstablished()) {
         for (List<sp<ABuffer> >::iterator it = mQueue.begin();
              it != mQueue.end(); ++it) {
             sp<AMessage> meta = (*it)->meta();
@@ -112,13 +112,6 @@
 }
 
 bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) {
-#if 1
-    if (mNumTimes != 2) {
-        // Drop incoming packets until we've established a time base.
-        return false;
-    }
-#endif
-
     uint32_t seqNum = (uint32_t)buffer->int32Data();
 
     if (mNumTimes == 2) {
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index c159e20..9bfd00f 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -266,6 +266,13 @@
         putUInt16(*values++);
 }
 
+void MtpDataPacket::putAUInt16(const UInt16List* values) {
+    size_t count = (values ? values->size() : 0);
+    putUInt32(count);
+    for (size_t i = 0; i < count; i++)
+        putUInt16((*values)[i]);
+}
+
 void MtpDataPacket::putAInt32(const int32_t* values, int count) {
     putUInt32(count);
     for (int i = 0; i < count; i++)
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index e8314d7..b458286 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -74,6 +74,7 @@
     void                putAUInt8(const uint8_t* values, int count);
     void                putAInt16(const int16_t* values, int count);
     void                putAUInt16(const uint16_t* values, int count);
+    void                putAUInt16(const UInt16List* values);
     void                putAInt32(const int32_t* values, int count);
     void                putAUInt32(const uint32_t* values, int count);
     void                putAUInt32(const UInt32List* list);
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index 02bb0d9..17823df 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -51,6 +51,13 @@
                                             MtpObjectFormat format,
                                             MtpObjectHandle parent) = 0;
 
+    // callee should delete[] the results from these
+    // results can be NULL
+    virtual MtpObjectFormatList*    getSupportedPlaybackFormats() = 0;
+    virtual MtpObjectFormatList*    getSupportedCaptureFormats() = 0;
+    virtual MtpObjectPropertyList*  getSupportedObjectProperties(MtpObjectFormat format) = 0;
+    virtual MtpDevicePropertyList*  getSupportedDeviceProperties() = 0;
+
     virtual MtpResponseCode         getObjectProperty(MtpObjectHandle handle,
                                             MtpObjectProperty property,
                                             MtpDataPacket& packet) = 0;
diff --git a/media/mtp/MtpDebug.h b/media/mtp/MtpDebug.h
index 3935d33..5b53e31 100644
--- a/media/mtp/MtpDebug.h
+++ b/media/mtp/MtpDebug.h
@@ -17,7 +17,7 @@
 #ifndef _MTP_DEBUG_H
 #define _MTP_DEBUG_H
 
-#define LOG_NDEBUG 0
+// #define LOG_NDEBUG 0
 #include <utils/Log.h>
 
 #include "MtpTypes.h"
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 082d924..30abfb8 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -78,46 +78,6 @@
     MTP_EVENT_OBJECT_REMOVED,
 };
 
-static const MtpObjectProperty kSupportedObjectProperties[] = {
-    MTP_PROPERTY_STORAGE_ID,
-    MTP_PROPERTY_OBJECT_FORMAT,
-    MTP_PROPERTY_OBJECT_SIZE,
-    MTP_PROPERTY_OBJECT_FILE_NAME,
-    MTP_PROPERTY_PARENT_OBJECT,
-};
-
-static const MtpObjectFormat kSupportedPlaybackFormats[] = {
-    // MTP_FORMAT_UNDEFINED,
-    MTP_FORMAT_ASSOCIATION,
-    // MTP_FORMAT_TEXT,
-    // MTP_FORMAT_HTML,
-    MTP_FORMAT_MP3,
-    //MTP_FORMAT_AVI,
-    MTP_FORMAT_MPEG,
-    // MTP_FORMAT_ASF,
-    MTP_FORMAT_EXIF_JPEG,
-    MTP_FORMAT_TIFF_EP,
-    // MTP_FORMAT_BMP,
-    MTP_FORMAT_GIF,
-    MTP_FORMAT_JFIF,
-    MTP_FORMAT_PNG,
-    MTP_FORMAT_TIFF,
-    MTP_FORMAT_WMA,
-    MTP_FORMAT_OGG,
-    MTP_FORMAT_AAC,
-    // MTP_FORMAT_FLAC,
-    // MTP_FORMAT_WMV,
-    MTP_FORMAT_MP4_CONTAINER,
-    MTP_FORMAT_MP2,
-    MTP_FORMAT_3GP_CONTAINER,
-    // MTP_FORMAT_ABSTRACT_AUDIO_ALBUM,
-    MTP_FORMAT_ABSTRACT_AV_PLAYLIST,
-    MTP_FORMAT_WPL_PLAYLIST,
-    MTP_FORMAT_M3U_PLAYLIST,
-    // MTP_FORMAT_MPL_PLAYLIST,
-    MTP_FORMAT_PLS_PLAYLIST,
-};
-
 MtpServer::MtpServer(int fd, MtpDatabase* database,
                     int fileGroup, int filePerm, int directoryPerm)
     :   mFD(fd),
@@ -354,6 +314,10 @@
     MtpStringBuffer   string;
     char prop_value[PROPERTY_VALUE_MAX];
 
+    MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
+    MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
+    MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
+
     // fill in device info
     mData.putUInt16(MTP_STANDARD_VERSION);
     mData.putUInt32(6); // MTP Vendor Extension ID
@@ -365,10 +329,9 @@
             sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
     mData.putAUInt16(kSupportedEventCodes,
             sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
-    mData.putEmptyArray(); // Device Properties Supported
-    mData.putEmptyArray(); // Capture Formats
-    mData.putAUInt16(kSupportedPlaybackFormats,
-            sizeof(kSupportedPlaybackFormats) / sizeof(uint16_t)); // Playback Formats
+    mData.putAUInt16(deviceProperties); // Device Properties Supported
+    mData.putAUInt16(captureFormats); // Capture Formats
+    mData.putAUInt16(playbackFormats);  // Playback Formats
     // FIXME
     string.set("Google, Inc.");
     mData.putString(string);   // Manufacturer
@@ -383,6 +346,10 @@
     string.set(prop_value);
     mData.putString(string);   // Serial Number
 
+    delete playbackFormats;
+    delete captureFormats;
+    delete deviceProperties;
+
     return MTP_RESPONSE_OK;
 }
 
@@ -443,8 +410,9 @@
     if (!mSessionOpen)
         return MTP_RESPONSE_SESSION_NOT_OPEN;
     MtpObjectFormat format = mRequest.getParameter(1);
-    mData.putAUInt16(kSupportedObjectProperties,
-            sizeof(kSupportedObjectProperties) / sizeof(uint16_t));
+    MtpDevicePropertyList* properties = mDatabase->getSupportedObjectProperties(format);
+    mData.putAUInt16(properties);
+    delete properties;
     return MTP_RESPONSE_OK;
 }
 
diff --git a/media/mtp/MtpTypes.h b/media/mtp/MtpTypes.h
index 2a895a7..7e3c009 100644
--- a/media/mtp/MtpTypes.h
+++ b/media/mtp/MtpTypes.h
@@ -78,6 +78,7 @@
 typedef Vector<int32_t> Int32List;
 typedef Vector<int64_t> Int64List;
 
+typedef UInt16List MtpObjectPropertyList;
 typedef UInt16List MtpDevicePropertyList;
 typedef UInt16List MtpObjectFormatList;
 typedef UInt32List MtpObjectHandleList;
diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml
index 246f9fc..f70a145 100644
--- a/media/tests/MediaFrameworkTest/AndroidManifest.xml
+++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml
@@ -51,4 +51,9 @@
          android:label="MediaRecorder stress tests InstrumentationRunner">
      </instrumentation>
 
+      <instrumentation android:name=".MediaFrameworkPowerTestRunner"
+         android:targetPackage="com.android.mediaframeworktest"
+         android:label="Media Power tests InstrumentationRunner">
+     </instrumentation>
+
 </manifest>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPowerTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPowerTestRunner.java
new file mode 100755
index 0000000..34db4db
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkPowerTestRunner.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.mediaframeworktest;
+
+import com.android.mediaframeworktest.power.MediaPlayerPowerTest;
+
+import junit.framework.TestSuite;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+
+/**
+ * Instrumentation Test Runner for all MediaPlayer tests.
+ *
+ * Running all tests:
+ *
+ * adb shell am instrument \
+ *   -w com.android.mediaframeworktest/.MediaFrameworkPowerTestRunner
+ */
+
+public class MediaFrameworkPowerTestRunner extends InstrumentationTestRunner {
+
+  @Override
+  public TestSuite getAllTests() {
+      TestSuite suite = new InstrumentationTestSuite(this);
+      suite.addTestSuite(MediaPlayerPowerTest.class);
+      return suite;
+  }
+
+  @Override
+  public ClassLoader getLoader() {
+      return MediaFrameworkPowerTestRunner.class.getClassLoader();
+  }
+}
+
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java
index 3e33951..c7f461e 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkTestRunner.java
@@ -25,6 +25,11 @@
 import com.android.mediaframeworktest.functional.SimTonesTest;
 import com.android.mediaframeworktest.functional.MediaPlayerInvokeTest;
 import com.android.mediaframeworktest.functional.MediaAudioManagerTest;
+import com.android.mediaframeworktest.functional.MediaAudioEffectTest;
+import com.android.mediaframeworktest.functional.MediaBassBoostTest;
+import com.android.mediaframeworktest.functional.MediaEqualizerTest;
+import com.android.mediaframeworktest.functional.MediaVirtualizerTest;
+import com.android.mediaframeworktest.functional.MediaVisualizerTest;
 import junit.framework.TestSuite;
 
 import android.test.InstrumentationTestRunner;
@@ -55,6 +60,11 @@
         suite.addTestSuite(MediaMimeTest.class);
         suite.addTestSuite(MediaPlayerInvokeTest.class);
         suite.addTestSuite(MediaAudioManagerTest.class);
+        suite.addTestSuite(MediaAudioEffectTest.class);
+        suite.addTestSuite(MediaBassBoostTest.class);
+        suite.addTestSuite(MediaEqualizerTest.class);
+        suite.addTestSuite(MediaVirtualizerTest.class);
+        suite.addTestSuite(MediaVisualizerTest.class);
         return suite;
     }
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index 9a48c92..ca6e999 100755
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -35,6 +35,7 @@
     public static final String WAV = "/sdcard/media_api/music/rings_2ch.wav";
     public static final String AMR = "/sdcard/media_api/music/test_amr_ietf.amr";
     public static final String OGG = "/sdcard/media_api/music/Revelation.ogg";
+    public static final String SINE_200_1000 = "/sdcard/media_api/music/sine_200+1000Hz_44K_mo.wav";
   
     public static final int MP3CBR_LENGTH = 71000;
     public static final int MP3VBR_LENGTH = 71000;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioEffectTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioEffectTest.java
new file mode 100644
index 0000000..fd939ae
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaAudioEffectTest.java
@@ -0,0 +1,1492 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mediaframeworktest.functional;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.EnvironmentalReverb;
+import android.media.Equalizer;
+import android.media.MediaPlayer;
+
+import android.os.Looper;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+/**
+ * Junit / Instrumentation test case for the media AudioTrack api
+
+ */
+public class MediaAudioEffectTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
+    private String TAG = "MediaAudioEffectTest";
+
+    private AudioEffect mEffect = null;
+    private boolean mHasControl = false;
+    private boolean mIsEnabled = false;
+    private int mParameterChanged = -1;
+    private MediaPlayer mMediaPlayer = null;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private int mError = 0;
+    private final Object lock = new Object();
+
+    public MediaAudioEffectTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+      super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private static void assumeTrue(String message, boolean cond) {
+        assertTrue("(assume)"+message, cond);
+    }
+
+    private void log(String testName, String message) {
+        Log.v(TAG, "["+testName+"] "+message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "["+testName+"] "+message);
+    }
+
+    //-----------------------------------------------------------------
+    // AUDIOEFFECT TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - static methods
+    //----------------------------------
+
+    //Test case 0.0: test queryEffects() and available effects
+    @LargeTest
+    public void test0_0QueryEffects() throws Exception {
+
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+
+        assertTrue("test0_0QueryEffects: number of effects < 4: "+desc.length, (desc.length >= 4));
+
+        boolean hasEQ = false;
+        boolean hasBassBoost = false;
+        boolean hasVirtualizer = false;
+        boolean hasEnvReverb = false;
+
+        for (int i = 0; i < desc.length; i++) {
+            if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) {
+                hasEQ = true;
+            } if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)) {
+                hasBassBoost = true;
+            } else if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) {
+                hasVirtualizer = true;
+            }
+            else if (desc[i].mType.equals(AudioEffect.EFFECT_TYPE_ENV_REVERB)) {
+                hasEnvReverb = true;
+            }
+        }
+        assertTrue("test0_0QueryEffects: equalizer not found", hasEQ);
+        assertTrue("test0_0QueryEffects: bass boost not found", hasBassBoost);
+        assertTrue("test0_0QueryEffects: virtualizer not found", hasVirtualizer);
+        assertTrue("test0_0QueryEffects: environmental reverb not found", hasEnvReverb);
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - constructor
+    //----------------------------------
+
+    //Test case 1.0: test constructor from effect type and get effect ID
+    @LargeTest
+    public void test1_0ConstructorFromType() throws Exception {
+        boolean result = true;
+        String msg = "test1_0ConstructorFromType()";
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+        assertTrue(msg+": no effects found", (desc.length != 0));
+        try {
+            AudioEffect effect = new AudioEffect(desc[0].mType,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            try {
+                assertTrue(msg +": invalid effect ID", (effect.getId() != 0));
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": AudioEffect not initialized");
+                result = false;
+            } finally {
+                effect.release();
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Effect not found: "+desc[0].mName);
+            result = false;
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            result = false;
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.1: test constructor from effect uuid
+    @LargeTest
+    public void test1_1ConstructorFromUuid() throws Exception {
+        boolean result = true;
+        String msg = "test1_1ConstructorFromUuid()";
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+        assertTrue(msg+"no effects found", (desc.length != 0));
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_NULL,
+                    desc[0].mUuid,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            effect.release();
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Effect not found: "+desc[0].mName);
+            result = false;
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            result = false;
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.2: test constructor failure from unknown type
+    @LargeTest
+    public void test1_2ConstructorUnknownType() throws Exception {
+        boolean result = false;
+        String msg = "test1_2ConstructorUnknownType()";
+
+        try {
+            AudioEffect effect = new AudioEffect(UUID.randomUUID(),
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            msg = msg.concat(": could create random AudioEffect");
+            if (effect != null) {
+                effect.release();
+            }
+        } catch (IllegalArgumentException e) {
+            result = true;
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.3: test getEnabled() failure when called on released effect
+    @LargeTest
+    public void test1_3GetEnabledAfterRelease() throws Exception {
+        boolean result = false;
+        String msg = "test1_3GetEnabledAfterRelease()";
+
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            effect.release();
+            try {
+                effect.getEnabled();
+            } catch (IllegalStateException e) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.4: test contructor on mediaPlayer audio session
+    @LargeTest
+    public void test1_4InsertOnMediaPlayer() throws Exception {
+        boolean result = false;
+        String msg = "test1_4InsertOnMediaPlayer()";
+
+        try {
+            MediaPlayer mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SHORTMP3);
+
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    mp.getAudioSessionId());
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            try {
+                loge(msg, ": effect.setEnabled");
+                effect.setEnabled(true);
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": AudioEffect not initialized");
+            }
+
+            result = true;
+            effect.release();
+            mp.release();
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.5: test auxiliary effect attachement on MediaPlayer
+    @LargeTest
+    public void test1_5AuxiliaryOnMediaPlayer() throws Exception {
+        boolean result = false;
+        String msg = "test1_5AuxiliaryOnMediaPlayer()";
+
+        try {
+            MediaPlayer mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SHORTMP3);
+
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            mp.attachAuxEffect(effect.getId());
+            mp.setAuxEffectSendLevel(1.0f);
+            result = true;
+            effect.release();
+            mp.release();
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.6: test auxiliary effect attachement failure before setDatasource
+    @LargeTest
+    public void test1_6AuxiliaryOnMediaPlayerFailure() throws Exception {
+        boolean result = false;
+        String msg = "test1_6AuxiliaryOnMediaPlayerFailure()";
+
+        try {
+            createMediaPlayerLooper();
+            synchronized(lock) {
+                try {
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Looper creation: wait was interrupted.");
+                }
+            }
+            assertTrue(mInitialized);  // mMediaPlayer has been initialized?
+            mError = 0;
+
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            synchronized(lock) {
+                try {
+                    mMediaPlayer.attachAuxEffect(effect.getId());
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Attach effect: wait was interrupted.");
+                }
+            }
+            assertTrue(msg + ": no error on attachAuxEffect", mError != 0);
+            result = true;
+            effect.release();
+            terminateMediaPlayerLooper();
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        }
+        assertTrue(msg, result);
+    }
+
+
+    //Test case 1.7: test auxiliary effect attachement on AudioTrack
+    @LargeTest
+    public void test1_7AuxiliaryOnAudioTrack() throws Exception {
+        boolean result = false;
+        String msg = "test1_7AuxiliaryOnAudioTrack()";
+
+        try {
+            AudioTrack track = new AudioTrack(
+                                        AudioManager.STREAM_MUSIC,
+                                        44100,
+                                        AudioFormat.CHANNEL_OUT_MONO,
+                                        AudioFormat.ENCODING_PCM_16BIT,
+                                        AudioTrack.getMinBufferSize(44100,
+                                                                    AudioFormat.CHANNEL_OUT_MONO,
+                                                                    AudioFormat.ENCODING_PCM_16BIT),
+                                                                    AudioTrack.MODE_STREAM);
+            assertNotNull(msg + ": could not create AudioTrack", track);
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+
+            track.attachAuxEffect(effect.getId());
+            track.setAuxEffectSendLevel(1.0f);
+            result = true;
+            effect.release();
+            track.release();
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - enable/ disable
+    //----------------------------------
+
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    @LargeTest
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        boolean result = false;
+        String msg = "test2_0SetEnabledGetEnabled()";
+
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            try {
+                effect.setEnabled(true);
+                assertTrue(msg + ": invalid state from getEnabled", effect.getEnabled());
+                effect.setEnabled(false);
+                assertFalse(msg + ": invalid state to getEnabled", effect.getEnabled());
+                result = true;
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": setEnabled() in wrong state");
+            } finally {
+                effect.release();
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    @LargeTest
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        boolean result = false;
+        String msg = "test2_1SetEnabledAfterRelease()";
+
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            effect.release();
+            try {
+                effect.setEnabled(true);
+            } catch (IllegalStateException e) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 3 - set parameters
+    //----------------------------------
+
+    //Test case 3.0: test setParameter(byte[], byte[])
+    @LargeTest
+    public void test3_0SetParameterByteArrayByteArray() throws Exception {
+        boolean result = false;
+        String msg = "test3_0SetParameterByteArrayByteArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            byte[] param = intToByteArray(Equalizer.PARAM_CURRENT_PRESET);
+            byte[] value = shortToByteArray((short)0);
+            if (effect.setParameter(param, value) == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.1: test setParameter(int, int)
+    @LargeTest
+    public void test3_1SetParameterIntInt() throws Exception {
+        boolean result = false;
+        String msg = "test3_1SetParameterIntInt()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            if (effect.setParameter(EnvironmentalReverb.PARAM_DECAY_TIME, 0)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.2: test setParameter(int, short)
+    @LargeTest
+    public void test3_2SetParameterIntShort() throws Exception {
+        boolean result = false;
+        String msg = "test3_2SetParameterIntShort()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            if (effect.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.3: test setParameter(int, byte[])
+    @LargeTest
+    public void test3_3SetParameterIntByteArray() throws Exception {
+        boolean result = false;
+        String msg = "test3_3SetParameterIntByteArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            byte[] value = shortToByteArray((short)0);
+            if (effect.setParameter(Equalizer.PARAM_CURRENT_PRESET, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.4: test setParameter(int[], int[])
+    @LargeTest
+    public void test3_4SetParameterIntArrayIntArray() throws Exception {
+        boolean result = false;
+        String msg = "test3_4SetParameterIntArrayIntArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] param = new int[1];
+            int[] value = new int[1];
+            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
+            value[0] = 0;
+            if (effect.setParameter(param, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.5: test setParameter(int[], short[])
+    @LargeTest
+    public void test3_5SetParameterIntArrayShortArray() throws Exception {
+        boolean result = false;
+        String msg = "test3_5SetParameterIntArrayShortArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] param = new int[1];
+            short[] value = new short[1];
+            param[0] = Equalizer.PARAM_CURRENT_PRESET;
+            value[0] = (short)0;
+            if (effect.setParameter(param, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.6: test setParameter(int[], byte[])
+    @LargeTest
+    public void test3_6SetParameterIntArrayByteArray() throws Exception {
+        boolean result = false;
+        String msg = "test3_6SetParameterIntArrayByteArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] param = new int[1];
+            byte[] value = shortToByteArray((short)0);
+            param[0] = Equalizer.PARAM_CURRENT_PRESET;
+            if (effect.setParameter(param, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("setParameter() called in wrong state");
+            loge(msg, "setParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 3.7: test setParameter() throws exception after release()
+    @LargeTest
+    public void test3_7SetParameterAfterRelease() throws Exception {
+        boolean result = false;
+        String msg = "test3_7SetParameterAfterRelease()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            effect.release();
+            effect.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0);
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": setParameter() rejected");
+            loge(msg, "setParameter() rejected");
+        } catch (IllegalStateException e) {
+            result = true;
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 4 - get parameters
+    //----------------------------------
+
+    //Test case 4.0: test getParameter(byte[], byte[])
+    @LargeTest
+    public void test4_0GetParameterByteArrayByteArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_0GetParameterByteArrayByteArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            byte[] param = intToByteArray(Equalizer.PARAM_CURRENT_PRESET);
+            byte[] value = new byte[2];
+            if (effect.getParameter(param, value) == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.1: test getParameter(int, int[])
+    @LargeTest
+    public void test4_1GetParameterIntIntArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_1GetParameterIntIntArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] value = new int[1];
+            if (effect.getParameter(EnvironmentalReverb.PARAM_DECAY_TIME, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.2: test getParameter(int, short[])
+    @LargeTest
+    public void test4_2GetParameterIntShortArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_2GetParameterIntShortArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            short[] value = new short[1];
+            if (effect.getParameter(Equalizer.PARAM_CURRENT_PRESET, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.3: test getParameter(int, byte[])
+    @LargeTest
+    public void test4_3GetParameterIntByteArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_3GetParameterIntByteArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            byte[] value = new byte[2];
+            if (effect.getParameter(Equalizer.PARAM_CURRENT_PRESET, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.4: test getParameter(int[], int[])
+    @LargeTest
+    public void test4_4GetParameterIntArrayIntArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_4GetParameterIntArrayIntArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] param = new int[1];
+            int[] value = new int[1];
+            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
+            if (effect.getParameter(param, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.5: test getParameter(int[], short[])
+    @LargeTest
+    public void test4_5GetParameterIntArrayShortArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_5GetParameterIntArrayShortArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] param = new int[1];
+            short[] value = new short[1];
+            param[0] = Equalizer.PARAM_CURRENT_PRESET;
+            if (effect.getParameter(param, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.6: test getParameter(int[], byte[])
+    @LargeTest
+    public void test4_6GetParameterIntArrayByteArray() throws Exception {
+        boolean result = false;
+        String msg = "test4_6GetParameterIntArrayByteArray()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            int[] param = new int[1];
+            byte[] value = new byte[2];
+            param[0] = Equalizer.PARAM_CURRENT_PRESET;
+            if (effect.getParameter(param, value)
+                    == AudioEffect.SUCCESS) {
+                result = true;
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("getParameter() called in wrong state");
+            loge(msg, "getParameter() called in wrong state");
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 4.7: test getParameter() throws exception after release()
+    @LargeTest
+    public void test4_7GetParameterAfterRelease() throws Exception {
+        boolean result = false;
+        String msg = "test4_7GetParameterAfterRelease()";
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            effect.release();
+            short[] value = new short[1];
+            effect.getParameter(Equalizer.PARAM_CURRENT_PRESET, value);
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": getParameter() rejected");
+            loge(msg, "getParameter() rejected");
+        } catch (IllegalStateException e) {
+            result = true;
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 5 priority and listeners
+    //----------------------------------
+
+    //Test case 5.0: test control passed to higher priority client
+    @LargeTest
+    public void test5_0setEnabledLowerPriority() throws Exception {
+        boolean result = false;
+        String msg = "test5_0setEnabledLowerPriority()";
+        AudioEffect effect1 = null;
+        AudioEffect effect2 = null;
+        try {
+            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+
+            assertNotNull(msg + ": could not create AudioEffect", effect1);
+            assertNotNull(msg + ": could not create AudioEffect", effect2);
+
+            assertTrue(msg + ": Effect2 does not have control", effect2.hasControl());
+            assertFalse(msg + ": Effect1 has control", effect1.hasControl());
+            assertTrue(msg + ": Effect1 can enable",
+                    effect1.setEnabled(true) == AudioEffect.ERROR_INVALID_OPERATION);
+            assertFalse(msg + ": Effect1 has enabled", effect2.getEnabled());
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Effect not found");
+            result = false;
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            result = false;
+        } finally {
+            if (effect1 != null) {
+                effect1.release();
+            }
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 5.1: test control passed to higher priority client
+    @LargeTest
+    public void test5_1setParameterLowerPriority() throws Exception {
+        boolean result = false;
+        String msg = "test5_1setParameterLowerPriority()";
+        AudioEffect effect1 = null;
+        AudioEffect effect2 = null;
+        try {
+            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+
+            assertNotNull(msg + ": could not create AudioEffect", effect1);
+            assertNotNull(msg + ": could not create AudioEffect", effect2);
+
+            int status = effect2.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0);
+            assertEquals(msg + ": Effect2 setParameter failed",
+                    AudioEffect.SUCCESS, status);
+
+            status = effect1.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)1);
+            assertEquals(msg + ": Effect1 setParameter did not fail",
+                    AudioEffect.ERROR_INVALID_OPERATION, status);
+
+            short[] value = new short[1];
+            status = effect2.getParameter(Equalizer.PARAM_CURRENT_PRESET, value);
+            assertEquals(msg + ": Effect2 getParameter failed",
+                    AudioEffect.SUCCESS, status);
+            assertEquals(msg + ": Effect1 changed parameter",
+                    (short)0, value[0]);
+
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Effect not found");
+            result = false;
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            result = false;
+        } finally {
+            if (effect1 != null) {
+                effect1.release();
+            }
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 5.2: test control status listener
+    @LargeTest
+    public void test5_2ControlStatusListener() throws Exception {
+        boolean result = false;
+        String msg = "test5_2ControlStatusListener()";
+        mEffect = null;
+        AudioEffect effect2 = null;
+        try {
+            mHasControl = true;
+            createListenerLooper(true, false, false);
+            synchronized(lock) {
+                try {
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Looper creation: wait was interrupted.");
+                }
+            }
+            assertTrue(mInitialized);
+            synchronized(lock) {
+                try {
+                    effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                            AudioEffect.EFFECT_TYPE_NULL,
+                            1,
+                            0);
+                    assertNotNull(msg + ": could not create AudioEffect", effect2);
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Create second effect: wait was interrupted.");
+                }
+            }
+            assertFalse(msg + ": effect control not lost by effect1", mHasControl);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        } finally {
+            terminateListenerLooper();
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 5.3: test enable status listener
+    @LargeTest
+    public void test5_3EnableStatusListener() throws Exception {
+        boolean result = false;
+        String msg = "test5_3EnableStatusListener()";
+        mEffect = null;
+        AudioEffect effect2 = null;
+        try {
+            createListenerLooper(false, true, false);
+            synchronized(lock) {
+                try {
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Looper creation: wait was interrupted.");
+                }
+            }
+            assertTrue(mInitialized);
+            mEffect.setEnabled(true);
+            mIsEnabled = true;
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect2);
+            assertTrue(msg + ": effect not enabled", effect2.getEnabled());
+            synchronized(lock) {
+                try {
+                    effect2.setEnabled(false);
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Create second effect: wait was interrupted.");
+                }
+            }
+            assertFalse(msg + ": enable status not updated", mIsEnabled);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        } finally {
+            terminateListenerLooper();
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 5.4: test parameter changed listener
+    @LargeTest
+    public void test5_4ParameterChangedListener() throws Exception {
+        boolean result = false;
+        String msg = "test5_4ParameterChangedListener()";
+        mEffect = null;
+        AudioEffect effect2 = null;
+        try {
+            createListenerLooper(false, false, true);
+            synchronized(lock) {
+                try {
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Looper creation: wait was interrupted.");
+                }
+            }
+            assertTrue(mInitialized);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect2);
+            synchronized(lock) {
+                try {
+                    mParameterChanged = -1;
+                    effect2.setParameter(Equalizer.PARAM_CURRENT_PRESET, (short)0);
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Create second effect: wait was interrupted.");
+                }
+            }
+            assertEquals(msg + ": parameter change not received",
+                    Equalizer.PARAM_CURRENT_PRESET, mParameterChanged);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        } finally {
+            terminateListenerLooper();
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 6 command method
+    //----------------------------------
+
+
+    //Test case 6.0: test command method
+    @LargeTest
+    public void test6_0Command() throws Exception {
+        boolean result = false;
+        String msg = "test6_0Command()";
+        AudioEffect effect = null;
+        try {
+             effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull(msg + ": could not create AudioEffect", effect);
+            try {
+                byte[] cmd = new byte[0];
+                byte[] reply = new byte[4];
+                int status = effect.command(3, cmd, reply);
+                assertEquals(msg + ": command failed", AudioEffect.SUCCESS, status);
+                assertTrue(msg + ": effect not enabled", effect.getEnabled());
+                result = true;
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": command in illegal state");
+            }
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+            loge(msg, ": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+            loge(msg, ": Effect library not loaded");
+        } catch (Exception e){
+            loge(msg, "Could not create media player:" + e);
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    /*
+     * Initializes the message looper so that the MediaPlayer object can
+     * receive the callback messages.
+     */
+    private void createMediaPlayerLooper() {
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mMediaPlayer = new MediaPlayer();
+                mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                    public boolean onError(MediaPlayer player, int what, int extra) {
+                        synchronized(lock) {
+                            mError = what;
+                            lock.notify();
+                        }
+                        return true;
+                    }
+                });
+                mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                    public void onCompletion(MediaPlayer player) {
+                        synchronized(lock) {
+                            lock.notify();
+                        }
+                    }
+                });
+                synchronized(lock) {
+                    mInitialized = true;
+                    lock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+    /*
+     * Terminates the message looper thread.
+     */
+    private void terminateMediaPlayerLooper() {
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+        }
+    }
+
+    /*
+     * Initializes the message looper fro effect listener
+     */
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+    }
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+
+        new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mEffect.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mEffect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                        AudioEffect.EFFECT_TYPE_NULL,
+                        0,
+                        0);
+                assertNotNull("could not create AudioEffect", mEffect);
+
+                if (mControl) {
+                    mEffect.setControlStatusListener(new AudioEffect.OnControlStatusChangeListener() {
+                        public void onControlStatusChange(AudioEffect effect, boolean controlGranted) {
+                            synchronized(lock) {
+                                if (effect == mEffect) {
+                                    mHasControl = controlGranted;
+                                    lock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mEnable) {
+                    mEffect.setEnableStatusListener(new AudioEffect.OnEnableStatusChangeListener() {
+                        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                            synchronized(lock) {
+                                if (effect == mEffect) {
+                                    mIsEnabled = enabled;
+                                    lock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+                if (mParameter) {
+                    mEffect.setParameterListener(new AudioEffect.OnParameterChangeListener() {
+                        public void onParameterChange(AudioEffect effect, int status, byte[] param,
+                                byte[] value) {
+                            synchronized(lock) {
+                                if (effect == mEffect) {
+                                    mParameterChanged = byteArrayToInt(param);
+                                    lock.notify();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                synchronized(lock) {
+                    mInitialized = true;
+                    lock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+    /*
+     * Terminates the listener looper thread.
+     */
+    private void terminateListenerLooper() {
+        if (mEffect != null) {
+            mEffect.release();
+            mEffect = null;
+        }
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+    protected int byteArrayToInt(byte[] valueBuf) {
+        return byteArrayToInt(valueBuf, 0);
+
+    }
+
+    protected int byteArrayToInt(byte[] valueBuf, int offset) {
+        ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+        converter.order(ByteOrder.nativeOrder());
+        return converter.getInt(offset);
+
+    }
+
+    protected byte[] intToByteArray(int value) {
+        ByteBuffer converter = ByteBuffer.allocate(4);
+        converter.order(ByteOrder.nativeOrder());
+        converter.putInt(value);
+        return converter.array();
+    }
+
+    protected short byteArrayToShort(byte[] valueBuf) {
+        return byteArrayToShort(valueBuf, 0);
+    }
+
+    protected short byteArrayToShort(byte[] valueBuf, int offset) {
+        ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+        converter.order(ByteOrder.nativeOrder());
+        return converter.getShort(offset);
+
+    }
+
+    protected byte[] shortToByteArray(short value) {
+        ByteBuffer converter = ByteBuffer.allocate(2);
+        converter.order(ByteOrder.nativeOrder());
+        short sValue = (short) value;
+        converter.putShort(sValue);
+        return converter.array();
+    }
+
+}
+
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaBassBoostTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaBassBoostTest.java
new file mode 100644
index 0000000..8a68c5e
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaBassBoostTest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mediaframeworktest.functional;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioEffect;
+import android.media.AudioManager;
+import android.media.BassBoost;
+import android.media.Visualizer;
+import android.media.MediaPlayer;
+
+import android.os.Looper;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+/**
+ * Junit / Instrumentation test case for the media AudioTrack api
+
+ */
+public class MediaBassBoostTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
+    private String TAG = "MediaBassBoostTest";
+    private final static int MIN_ENERGY_RATIO_2 = 4;
+    private final static short TEST_STRENGTH = 500;
+
+    private BassBoost mBassBoost = null;
+    private int mSession = -1;
+
+    public MediaBassBoostTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+      super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        releaseBassBoost();
+    }
+
+    private static void assumeTrue(String message, boolean cond) {
+        assertTrue("(assume)"+message, cond);
+    }
+
+    private void log(String testName, String message) {
+        Log.v(TAG, "["+testName+"] "+message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "["+testName+"] "+message);
+    }
+
+    //-----------------------------------------------------------------
+    // BASS BOOST TESTS:
+    //----------------------------------
+
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @LargeTest
+    public void test0_0ConstructorAndRelease() throws Exception {
+        boolean result = false;
+        String msg = "test1_0ConstructorAndRelease()";
+        BassBoost bb = null;
+         try {
+            bb = new BassBoost(0, 0);
+            assertNotNull(msg + ": could not create BassBoost", bb);
+            try {
+                assertTrue(msg +": invalid effect ID", (bb.getId() != 0));
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": BassBoost not initialized");
+            }
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": BassBoost not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+        } finally {
+            if (bb != null) {
+                bb.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test strength
+    @LargeTest
+    public void test1_0Strength() throws Exception {
+        boolean result = false;
+        String msg = "test1_0Strength()";
+        getBassBoost(0);
+        try {
+            if (mBassBoost.getStrengthSupported()) {
+                mBassBoost.setStrength((short)TEST_STRENGTH);
+                short strength = mBassBoost.getRoundedStrength();
+                // allow 10% difference between set strength and rounded strength
+                assertTrue(msg +": got incorrect strength",
+                        ((float)strength > (float)TEST_STRENGTH * 0.9f) &&
+                        ((float)strength < (float)TEST_STRENGTH * 1.1f));
+            } else {
+                short strength = mBassBoost.getRoundedStrength();
+                assertTrue(msg +": got incorrect strength", strength >= 0 && strength <= 1000);
+            }
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.1: test properties
+    @LargeTest
+    public void test1_1Properties() throws Exception {
+        boolean result = false;
+        String msg = "test1_1Properties()";
+        getBassBoost(0);
+        try {
+            BassBoost.Settings settings = mBassBoost.getProperties();
+            String str = settings.toString();
+            settings = new BassBoost.Settings(str);
+            mBassBoost.setProperties(settings);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect action
+    //----------------------------------
+
+    //Test case 2.0: test actual bass boost influence on sound
+    @LargeTest
+    public void test2_0SoundModification() throws Exception {
+        boolean result = false;
+        String msg = "test2_0SoundModification()";
+        EnergyProbe probe = null;
+        AudioEffect vc = null;
+        MediaPlayer mp = null;
+        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
+                           0);
+
+        try {
+            probe = new EnergyProbe(0);
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                      0,
+                      0);
+            vc.setEnabled(true);
+
+            mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SINE_200_1000);
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            getBassBoost(mp.getAudioSessionId());
+            mp.prepare();
+            mp.start();
+            Thread.sleep(200);
+            // measure reference energy around 1kHz
+            int refEnergy200 = probe.capture(200);
+            int refEnergy1000 = probe.capture(1000);
+            mBassBoost.setStrength((short)1000);
+            mBassBoost.setEnabled(true);
+            Thread.sleep(500);
+            // measure energy around 1kHz with band level at min
+            int energy200 = probe.capture(200);
+            int energy1000 = probe.capture(1000);
+            // verify that the energy ration between low and high frequencies is at least
+            // MIN_ENERGY_RATIO_2 times higher with bassboost on.
+            assertTrue(msg + ": bass boost has no effect",
+                    ((float)energy200/(float)energy1000) >
+                    (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000)));
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            loge(msg, "sleep() interrupted");
+        }
+        finally {
+            releaseBassBoost();
+            if (mp != null) {
+                mp.release();
+            }
+            if (vc != null) {
+                vc.release();
+            }
+            if (probe != null) {
+                probe.release();
+            }
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+        }
+        assertTrue(msg, result);
+    }
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private class EnergyProbe {
+        Visualizer mVisualizer = null;
+        private byte[] mFft = new byte[1024];
+
+        public EnergyProbe(int session) {
+            mVisualizer = new Visualizer(session);
+            mVisualizer.setCaptureSize(1024);
+        }
+
+        public int capture(int freq) throws InterruptedException {
+            int energy = 0;
+            int count = 0;
+            if (mVisualizer != null) {
+                mVisualizer.setEnabled(true);
+                for (int i = 0; i < 10; i++) {
+                    if (mVisualizer.getFft(mFft) == Visualizer.SUCCESS) {
+                        // TODO: check speex FFT as it seems to return only the number of points
+                        // correspondong to valid part of the spectrum (< Fs).
+                        // e.g., if the number of points is 1024, it covers the frequency range
+                        // 0 to 22050 instead of 0 to 44100 as expected from an FFT.
+                        int bin = freq / (22050 / 1024);
+                        int tmp = 0;
+                        for (int j = bin-2; j < bin+3; j++) {
+                            tmp += (int)mFft[j] * (int)mFft[j];
+                        }
+                        energy += tmp/5;
+                        count++;
+                    }
+                    Thread.sleep(50);
+                }
+                mVisualizer.setEnabled(false);
+            }
+            if (count == 0) {
+                return 0;
+            }
+            return energy/count;
+        }
+
+        public void release() {
+            if (mVisualizer != null) {
+                mVisualizer.release();
+                mVisualizer = null;
+            }
+        }
+    }
+
+    private void getBassBoost(int session) {
+         if (mBassBoost == null || session != mSession) {
+             if (session != mSession && mBassBoost != null) {
+                 mBassBoost.release();
+                 mBassBoost = null;
+             }
+             try {
+                mBassBoost = new BassBoost(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getBassBoost() BassBoost not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getBassBoost() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mBassBoost", mBassBoost);
+    }
+
+    private void releaseBassBoost() {
+        if (mBassBoost != null) {
+            mBassBoost.release();
+            mBassBoost = null;
+        }
+   }
+
+}
+
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaEqualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaEqualizerTest.java
new file mode 100644
index 0000000..e46887b
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaEqualizerTest.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mediaframeworktest.functional;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioEffect;
+import android.media.AudioManager;
+import android.media.Equalizer;
+import android.media.Visualizer;
+import android.media.MediaPlayer;
+
+import android.os.Looper;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+/**
+ * Junit / Instrumentation test case for the media AudioTrack api
+
+ */
+public class MediaEqualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
+    private String TAG = "MediaEqualizerTest";
+    private final static int MIN_NUMBER_OF_BANDS = 4;
+    private final static int MIN_BAND_LEVEL = -1500;
+    private final static int MAX_BAND_LEVEL = 1500;
+    private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000;
+    private final static int MIN_NUMBER_OF_PRESETS = 4;
+    private Equalizer mEqualizer = null;
+    private int mSession = -1;
+
+    public MediaEqualizerTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+      super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        releaseEqualizer();
+    }
+
+    private static void assumeTrue(String message, boolean cond) {
+        assertTrue("(assume)"+message, cond);
+    }
+
+    private void log(String testName, String message) {
+        Log.v(TAG, "["+testName+"] "+message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "["+testName+"] "+message);
+    }
+
+    //-----------------------------------------------------------------
+    // EQUALIZER TESTS:
+    //----------------------------------
+
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @LargeTest
+    public void test0_0ConstructorAndRelease() throws Exception {
+        boolean result = false;
+        String msg = "test1_0ConstructorAndRelease()";
+        Equalizer eq = null;
+         try {
+            eq = new Equalizer(0, 0);
+            assertNotNull(msg + ": could not create Equalizer", eq);
+            try {
+                assertTrue(msg +": invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": Equalizer not initialized");
+            }
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test setBandLevel() and getBandLevel()
+    @LargeTest
+    public void test1_0BandLevel() throws Exception {
+        boolean result = false;
+        String msg = "test1_0BandLevel()";
+        getEqualizer(0);
+        try {
+            short numBands = mEqualizer.getNumberOfBands();
+            assertTrue(msg + ": not enough bands", numBands >= MIN_NUMBER_OF_BANDS);
+
+            short[] levelRange = mEqualizer.getBandLevelRange();
+            assertTrue(msg + ": min level too high", levelRange[0] <= MIN_BAND_LEVEL);
+            assertTrue(msg + ": max level too low", levelRange[1] >= MAX_BAND_LEVEL);
+
+            mEqualizer.setBandLevel((short)0, levelRange[1]);
+            short level = mEqualizer.getBandLevel((short)0);
+            // 10% margin on actual level compared to requested level
+            assertTrue(msg + ": setBandLevel failed",
+                    ((float)level > (float)levelRange[1] * 0.9f) &&
+                    ((float)level < (float)levelRange[1] * 1.1f));
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.1: test band frequency
+    @LargeTest
+    public void test1_1BandFrequency() throws Exception {
+        boolean result = false;
+        String msg = "test1_1BandFrequency()";
+        getEqualizer(0);
+        try {
+            short band = mEqualizer.getBand(TEST_FREQUENCY_MILLIHERTZ);
+            assertTrue(msg + ": getBand failed", band >= 0);
+            int[] freqRange = mEqualizer.getBandFreqRange(band);
+            assertTrue(msg + ": getBandFreqRange failed",
+                    (freqRange[0] <= TEST_FREQUENCY_MILLIHERTZ) &&
+                    (freqRange[1] >= TEST_FREQUENCY_MILLIHERTZ));
+            int freq = mEqualizer.getCenterFreq(band);
+            assertTrue(msg + ": getCenterFreq failed",
+                    (freqRange[0] <= freq) && (freqRange[1] >= freq));
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.2: test presets
+    @LargeTest
+    public void test1_2Presets() throws Exception {
+        boolean result = false;
+        String msg = "test1_2Presets()";
+        getEqualizer(0);
+        try {
+            short numPresets = mEqualizer.getNumberOfPresets();
+            assertTrue(msg + ": getNumberOfPresets failed", numPresets >= MIN_NUMBER_OF_PRESETS);
+            mEqualizer.usePreset((short)(numPresets - 1));
+            short preset = mEqualizer.getCurrentPreset();
+            assertEquals(msg + ": usePreset failed", preset, (short)(numPresets - 1));
+            String name = mEqualizer.getPresetName(preset);
+            assertNotNull(msg + ": getPresetName failed", name);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.3: test properties
+    @LargeTest
+    public void test1_3Properties() throws Exception {
+        boolean result = false;
+        String msg = "test1_3Properties()";
+        getEqualizer(0);
+        try {
+            Equalizer.Settings settings = mEqualizer.getProperties();
+            String str = settings.toString();
+            settings = new Equalizer.Settings(str);
+            mEqualizer.setProperties(settings);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect action
+    //----------------------------------
+
+    //Test case 2.0: test that the equalizer actually alters the sound
+    @LargeTest
+    public void test2_0SoundModification() throws Exception {
+        boolean result = false;
+        String msg = "test2_0SoundModification()";
+        EnergyProbe probe = null;
+        AudioEffect vc = null;
+        MediaPlayer mp = null;
+        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
+                           0);
+        try {
+            probe = new EnergyProbe(0);
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                      0,
+                      0);
+            vc.setEnabled(true);
+
+            mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SINE_200_1000);
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            getEqualizer(mp.getAudioSessionId());
+            mp.prepare();
+            mp.start();
+            Thread.sleep(500);
+            // measure reference energy around 1kHz
+            int refEnergy = probe.capture(1000);
+            short band = mEqualizer.getBand(1000000);
+            short[] levelRange = mEqualizer.getBandLevelRange();
+            mEqualizer.setBandLevel(band, levelRange[0]);
+            mEqualizer.setEnabled(true);
+            Thread.sleep(500);
+            // measure energy around 1kHz with band level at min
+            int energy = probe.capture(1000);
+            assertTrue(msg + ": equalizer has no effect at 1kHz", energy < refEnergy/4);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            loge(msg, "sleep() interrupted");
+        }
+        finally {
+            releaseEqualizer();
+            if (mp != null) {
+                mp.release();
+            }
+            if (vc != null) {
+                vc.release();
+            }
+            if (probe != null) {
+                probe.release();
+            }
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private class EnergyProbe {
+        Visualizer mVisualizer = null;
+        private byte[] mFft = new byte[1024];
+
+        public EnergyProbe(int session) {
+            mVisualizer = new Visualizer(session);
+            mVisualizer.setCaptureSize(1024);
+        }
+
+        public int capture(int freq) throws InterruptedException {
+            int energy = 0;
+            int count = 0;
+            if (mVisualizer != null) {
+                mVisualizer.setEnabled(true);
+                for (int i = 0; i < 10; i++) {
+                    if (mVisualizer.getFft(mFft) == Visualizer.SUCCESS) {
+                        // TODO: check speex FFT as it seems to return only the number of points
+                        // correspondong to valid part of the spectrum (< Fs).
+                        // e.g., if the number of points is 1024, it covers the frequency range
+                        // 0 to 22050 instead of 0 to 44100 as expected from an FFT.
+                        int bin = freq / (22050 / 1024);
+                        int tmp = 0;
+                        for (int j = bin-2; j < bin+3; j++) {
+                            tmp += (int)mFft[j] * (int)mFft[j];
+                        }
+                        energy += tmp/5;
+                        count++;
+                    }
+                    Thread.sleep(50);
+                }
+                mVisualizer.setEnabled(false);
+            }
+            if (count == 0) {
+                return 0;
+            }
+            return energy/count;
+        }
+
+        public void release() {
+            if (mVisualizer != null) {
+                mVisualizer.release();
+                mVisualizer = null;
+            }
+        }
+    }
+
+    private void getEqualizer(int session) {
+         if (mEqualizer == null || session != mSession) {
+             if (session != mSession && mEqualizer != null) {
+                 mEqualizer.release();
+                 mEqualizer = null;
+             }
+             try {
+                mEqualizer = new Equalizer(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getEqualizer() Equalizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getEqualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mEqualizer", mEqualizer);
+    }
+
+    private void releaseEqualizer() {
+        if (mEqualizer != null) {
+            mEqualizer.release();
+            mEqualizer = null;
+        }
+   }
+
+}
+
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVirtualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVirtualizerTest.java
new file mode 100644
index 0000000..6b8ae44
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVirtualizerTest.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mediaframeworktest.functional;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioEffect;
+import android.media.AudioManager;
+import android.media.Virtualizer;
+import android.media.Visualizer;
+import android.media.MediaPlayer;
+
+import android.os.Looper;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+/**
+ * Junit / Instrumentation test case for the media AudioTrack api
+
+ */
+public class MediaVirtualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
+    private String TAG = "MediaVirtualizerTest";
+    private final static int MIN_ENERGY_RATIO_2 = 4;
+    private final static short TEST_STRENGTH = 500;
+
+    private Virtualizer mVirtualizer = null;
+    private int mSession = -1;
+
+    public MediaVirtualizerTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+      super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        releaseVirtualizer();
+    }
+
+    private static void assumeTrue(String message, boolean cond) {
+        assertTrue("(assume)"+message, cond);
+    }
+
+    private void log(String testName, String message) {
+        Log.v(TAG, "["+testName+"] "+message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "["+testName+"] "+message);
+    }
+
+    //-----------------------------------------------------------------
+    // VIRTUALIZER TESTS:
+    //----------------------------------
+
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @LargeTest
+    public void test0_0ConstructorAndRelease() throws Exception {
+        boolean result = false;
+        String msg = "test1_0ConstructorAndRelease()";
+        Virtualizer virtualizer = null;
+         try {
+            virtualizer = new Virtualizer(0, 0);
+            assertNotNull(msg + ": could not create Virtualizer", virtualizer);
+            try {
+                assertTrue(msg +": invalid effect ID", (virtualizer.getId() != 0));
+            } catch (IllegalStateException e) {
+                msg = msg.concat(": Virtualizer not initialized");
+            }
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Virtualizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+        } finally {
+            if (virtualizer != null) {
+                virtualizer.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test strength
+    @LargeTest
+    public void test1_0Strength() throws Exception {
+        boolean result = false;
+        String msg = "test1_0Strength()";
+        getVirtualizer(0);
+        try {
+            if (mVirtualizer.getStrengthSupported()) {
+                mVirtualizer.setStrength((short)TEST_STRENGTH);
+                short strength = mVirtualizer.getRoundedStrength();
+                // allow 10% difference between set strength and rounded strength
+                assertTrue(msg +": got incorrect strength",
+                        ((float)strength > (float)TEST_STRENGTH * 0.9f) &&
+                        ((float)strength < (float)TEST_STRENGTH * 1.1f));
+            } else {
+                short strength = mVirtualizer.getRoundedStrength();
+                assertTrue(msg +": got incorrect strength", strength >= 0 && strength <= 1000);
+            }
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.1: test properties
+    @LargeTest
+    public void test1_1Properties() throws Exception {
+        boolean result = false;
+        String msg = "test1_1Properties()";
+        getVirtualizer(0);
+        try {
+            Virtualizer.Settings settings = mVirtualizer.getProperties();
+            String str = settings.toString();
+            settings = new Virtualizer.Settings(str);
+            mVirtualizer.setProperties(settings);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect action
+    //----------------------------------
+
+    //Test case 2.0: test actual virtualizer influence on sound
+    @LargeTest
+    public void test2_0SoundModification() throws Exception {
+        boolean result = false;
+        String msg = "test2_0SoundModification()";
+        EnergyProbe probe = null;
+        AudioEffect vc = null;
+        MediaPlayer mp = null;
+        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
+                           0);
+
+        try {
+            probe = new EnergyProbe(0);
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                      0,
+                      0);
+            vc.setEnabled(true);
+
+            mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SINE_200_1000);
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            getVirtualizer(mp.getAudioSessionId());
+            mp.prepare();
+            mp.start();
+            Thread.sleep(200);
+            // measure reference energy around 1kHz
+            int refEnergy200 = probe.capture(200);
+            int refEnergy1000 = probe.capture(1000);
+            mVirtualizer.setStrength((short)1000);
+            mVirtualizer.setEnabled(true);
+            Thread.sleep(500);
+            // measure energy around 1kHz with band level at min
+            int energy200 = probe.capture(200);
+            int energy1000 = probe.capture(1000);
+            // verify that the energy ration between low and high frequencies is at least
+            // four times higher with virtualizer on.
+            // NOTE: this is what is observed with current virtualizer implementation and the test
+            // audio file but is not the primary effect of the virtualizer. A better way would
+            // be to have a stereo PCM capture and check that a strongly paned input is centered
+            // when output. However, we cannot capture stereo with the visualizer.
+            assertTrue(msg + ": virtiualizer has no effect",
+                    ((float)energy200/(float)energy1000) >
+                    (MIN_ENERGY_RATIO_2 * ((float)refEnergy200/(float)refEnergy1000)));
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            loge(msg, "sleep() interrupted");
+        }
+        finally {
+            releaseVirtualizer();
+            if (mp != null) {
+                mp.release();
+            }
+            if (vc != null) {
+                vc.release();
+            }
+            if (probe != null) {
+                probe.release();
+            }
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+        }
+        assertTrue(msg, result);
+    }
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private class EnergyProbe {
+        Visualizer mVisualizer = null;
+        private byte[] mFft = new byte[1024];
+
+        public EnergyProbe(int session) {
+            mVisualizer = new Visualizer(session);
+            mVisualizer.setCaptureSize(1024);
+        }
+
+        public int capture(int freq) throws InterruptedException {
+            int energy = 0;
+            int count = 0;
+            if (mVisualizer != null) {
+                mVisualizer.setEnabled(true);
+                for (int i = 0; i < 10; i++) {
+                    if (mVisualizer.getFft(mFft) == Visualizer.SUCCESS) {
+                        // TODO: check speex FFT as it seems to return only the number of points
+                        // correspondong to valid part of the spectrum (< Fs).
+                        // e.g., if the number of points is 1024, it covers the frequency range
+                        // 0 to 22050 instead of 0 to 44100 as expected from an FFT.
+                        int bin = freq / (22050 / 1024);
+                        int tmp = 0;
+                        for (int j = bin-2; j < bin+3; j++) {
+                            tmp += (int)mFft[j] * (int)mFft[j];
+                        }
+                        energy += tmp/5;
+                        count++;
+                    }
+                    Thread.sleep(50);
+                }
+                mVisualizer.setEnabled(false);
+            }
+            if (count == 0) {
+                return 0;
+            }
+            return energy/count;
+        }
+
+        public void release() {
+            if (mVisualizer != null) {
+                mVisualizer.release();
+                mVisualizer = null;
+            }
+        }
+    }
+
+    private void getVirtualizer(int session) {
+         if (mVirtualizer == null || session != mSession) {
+             if (session != mSession && mVirtualizer != null) {
+                 mVirtualizer.release();
+                 mVirtualizer = null;
+             }
+             try {
+                mVirtualizer = new Virtualizer(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getVirtualizer() Virtualizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getVirtualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mVirtualizer", mVirtualizer);
+    }
+
+    private void releaseVirtualizer() {
+        if (mVirtualizer != null) {
+            mVirtualizer.release();
+            mVirtualizer = null;
+        }
+   }
+
+}
+
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVisualizerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVisualizerTest.java
new file mode 100644
index 0000000..26fdbfe
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/MediaVisualizerTest.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.mediaframeworktest.functional;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioEffect;
+import android.media.AudioManager;
+import android.media.Visualizer;
+import android.media.MediaPlayer;
+
+import android.os.Looper;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.Suppress;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+/**
+ * Junit / Instrumentation test case for the media AudioTrack api
+
+ */
+public class MediaVisualizerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
+    private String TAG = "MediaVisualizerTest";
+    private final static int MIN_CAPTURE_RATE_MAX = 20000;
+    private final static int MIN_SAMPLING_RATE = 8000000;
+    private final static int MAX_SAMPLING_RATE = 48000000;
+    private final static int MIN_CAPTURE_SIZE_MAX = 1024;
+    private final static int MAX_CAPTURE_SIZE_MIN = 128;
+
+    private Visualizer mVisualizer = null;
+    private int mSession = -1;
+    private boolean mInitialized = false;
+    private Looper mLooper = null;
+    private final Object lock = new Object();
+    private byte[] mWaveform = null;
+    private byte[] mFft = null;
+    private boolean mCaptureWaveform = false;
+    private boolean mCaptureFft = false;
+
+    public MediaVisualizerTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+      super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        releaseVisualizer();
+    }
+
+    private static void assumeTrue(String message, boolean cond) {
+        assertTrue("(assume)"+message, cond);
+    }
+
+    private void log(String testName, String message) {
+        Log.v(TAG, "["+testName+"] "+message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "["+testName+"] "+message);
+    }
+
+    //-----------------------------------------------------------------
+    // VISUALIZER TESTS:
+    //----------------------------------
+
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    @LargeTest
+    public void test0_0ConstructorAndRelease() throws Exception {
+        boolean result = false;
+        String msg = "test1_0ConstructorAndRelease()";
+        Visualizer visualizer = null;
+         try {
+            visualizer = new Visualizer(0);
+            assertNotNull(msg + ": could not create Visualizer", visualizer);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Visualizer not found");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": Effect library not loaded");
+        } finally {
+            if (visualizer != null) {
+                visualizer.release();
+            }
+        }
+        assertTrue(msg, result);
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: check capture rate and sampling rate
+    @LargeTest
+    public void test1_0CaptureRates() throws Exception {
+        boolean result = false;
+        String msg = "test1_0CaptureRates()";
+        getVisualizer(0);
+        try {
+            int captureRate = mVisualizer.getMaxCaptureRate();
+            assertTrue(msg +": insufficient max capture rate",
+                    captureRate >= MIN_CAPTURE_RATE_MAX);
+            int samplingRate = mVisualizer.getSamplingRate();
+            assertTrue(msg +": invalid sampling rate",
+                    samplingRate >= MIN_SAMPLING_RATE && samplingRate <= MAX_SAMPLING_RATE);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseVisualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 1.1: check capture size
+    @LargeTest
+    public void test1_1CaptureSize() throws Exception {
+        boolean result = false;
+        String msg = "test1_1CaptureSize()";
+        getVisualizer(0);
+        try {
+            int[] range = mVisualizer.getCaptureSizeRange();
+            assertTrue(msg +": insufficient min capture size",
+                    range[0] <= MAX_CAPTURE_SIZE_MIN);
+            assertTrue(msg +": insufficient min capture size",
+                    range[1] >= MIN_CAPTURE_SIZE_MAX);
+            mVisualizer.setCaptureSize(range[0]);
+            assertEquals(msg +": insufficient min capture size",
+                    range[0], mVisualizer.getCaptureSize());
+            mVisualizer.setCaptureSize(range[1]);
+            assertEquals(msg +": insufficient min capture size",
+                    range[1], mVisualizer.getCaptureSize());
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } finally {
+            releaseVisualizer();
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - check capture
+    //----------------------------------
+
+    //Test case 2.0: test capture in polling mode
+    @LargeTest
+    public void test2_0PollingCapture() throws Exception {
+        boolean result = false;
+        String msg = "test2_0PollingCapture()";
+        AudioEffect vc = null;
+        MediaPlayer mp = null;
+        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
+                           0);
+
+        try {
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                      0,
+                      0);
+            vc.setEnabled(true);
+
+            mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SINE_200_1000);
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            getVisualizer(mp.getAudioSessionId());
+            mVisualizer.setEnabled(true);
+            // check capture on silence
+            byte[] data = new byte[mVisualizer.getCaptureSize()];
+            mVisualizer.getWaveForm(data);
+            int energy = computeEnergy(data, true);
+            assertEquals(msg +": getWaveForm reports energy for silence",
+                    0, energy);
+            mVisualizer.getFft(data);
+            energy = computeEnergy(data, false);
+            assertEquals(msg +": getFft reports energy for silence",
+                    0, energy);
+            mp.prepare();
+            mp.start();
+            Thread.sleep(500);
+            // check capture on sound
+            mVisualizer.getWaveForm(data);
+            energy = computeEnergy(data, true);
+            assertTrue(msg +": getWaveForm reads insufficient level",
+                    energy > 0);
+            mVisualizer.getFft(data);
+            energy = computeEnergy(data, false);
+            assertTrue(msg +": getFft reads insufficient level",
+                    energy > 0);
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            loge(msg, "sleep() interrupted");
+        }
+        finally {
+            releaseVisualizer();
+            if (mp != null) {
+                mp.release();
+            }
+            if (vc != null) {
+                vc.release();
+            }
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+        }
+        assertTrue(msg, result);
+    }
+
+    //Test case 2.1: test capture with listener
+    @LargeTest
+    public void test2_1ListenerCapture() throws Exception {
+        boolean result = false;
+        String msg = "test2_1ListenerCapture()";
+        AudioEffect vc = null;
+        MediaPlayer mp = null;
+        AudioManager am = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
+        int volume = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                           am.getStreamMaxVolume(AudioManager.STREAM_MUSIC),
+                           0);
+
+        try {
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                      0,
+                      0);
+            vc.setEnabled(true);
+
+            mp = new MediaPlayer();
+            mp.setDataSource(MediaNames.SINE_200_1000);
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+
+            getVisualizer(mp.getAudioSessionId());
+            createListenerLooper();
+            synchronized(lock) {
+                try {
+                    lock.wait(1000);
+                } catch(Exception e) {
+                    Log.e(TAG, "Looper creation: wait was interrupted.");
+                }
+            }
+            assertTrue(mInitialized);
+
+            mVisualizer.setEnabled(true);
+
+            // check capture on silence
+            synchronized(lock) {
+                try {
+                    mCaptureWaveform = true;
+                    lock.wait(1000);
+                    mCaptureWaveform = false;
+                } catch(Exception e) {
+                    Log.e(TAG, "Capture waveform: wait was interrupted.");
+                }
+            }
+            assertNotNull(msg +": waveform capture failed", mWaveform);
+            int energy = computeEnergy(mWaveform, true);
+            assertEquals(msg +": getWaveForm reports energy for silence",
+                    0, energy);
+
+            synchronized(lock) {
+                try {
+                    mCaptureFft = true;
+                    lock.wait(1000);
+                    mCaptureFft = false;
+                } catch(Exception e) {
+                    Log.e(TAG, "Capture FFT: wait was interrupted.");
+                }
+            }
+            assertNotNull(msg +": FFT capture failed", mFft);
+            energy = computeEnergy(mFft, false);
+            assertEquals(msg +": getFft reports energy for silence",
+                    0, energy);
+
+            mp.prepare();
+            mp.start();
+            Thread.sleep(500);
+
+            // check capture on sound
+            synchronized(lock) {
+                try {
+                    mCaptureWaveform = true;
+                    lock.wait(1000);
+                    mCaptureWaveform = false;
+                } catch(Exception e) {
+                    Log.e(TAG, "Capture waveform: wait was interrupted.");
+                }
+            }
+            assertNotNull(msg +": waveform capture failed", mWaveform);
+            energy = computeEnergy(mWaveform, true);
+            assertTrue(msg +": getWaveForm reads insufficient level",
+                    energy > 0);
+
+            synchronized(lock) {
+                try {
+                    mCaptureFft = true;
+                    lock.wait(1000);
+                    mCaptureFft = false;
+                } catch(Exception e) {
+                    Log.e(TAG, "Capture FFT: wait was interrupted.");
+                }
+            }
+            assertNotNull(msg +": FFT capture failed", mFft);
+            energy = computeEnergy(mFft, false);
+            assertTrue(msg +": getFft reads insufficient level",
+                    energy > 0);
+
+            result = true;
+        } catch (IllegalArgumentException e) {
+            msg = msg.concat(": Bad parameter value");
+            loge(msg, "Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            msg = msg.concat(": get parameter() rejected");
+            loge(msg, "get parameter() rejected");
+        } catch (IllegalStateException e) {
+            msg = msg.concat("get parameter() called in wrong state");
+            loge(msg, "get parameter() called in wrong state");
+        } catch (InterruptedException e) {
+            loge(msg, "sleep() interrupted");
+        }
+        finally {
+            terminateListenerLooper();
+            releaseVisualizer();
+            if (mp != null) {
+                mp.release();
+            }
+            if (vc != null) {
+                vc.release();
+            }
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+        }
+        assertTrue(msg, result);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private int computeEnergy(byte[] data, boolean unsigned) {
+        int energy = 0;
+        if (data.length != 0) {
+            for (int i = 0; i < data.length; i++) {
+                int tmp;
+                // convert from unsigned 8 bit to signed 16 bit
+                if (unsigned) {
+                    tmp = ((int)data[i] & 0xFF) - 128;
+                } else {
+                    tmp = (int)data[i];
+                }
+                energy += tmp*tmp;
+            }
+            energy /= data.length;
+        }
+        return energy;
+    }
+
+    private void getVisualizer(int session) {
+         if (mVisualizer == null || session != mSession) {
+             if (session != mSession && mVisualizer != null) {
+                 mVisualizer.release();
+                 mVisualizer = null;
+             }
+             try {
+                mVisualizer = new Visualizer(session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mVisualizer", mVisualizer);
+    }
+
+    private void releaseVisualizer() {
+        if (mVisualizer != null) {
+            mVisualizer.release();
+            mVisualizer = null;
+        }
+   }
+
+    private void createListenerLooper() {
+
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mEffect.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                if (mVisualizer != null) {
+                    mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
+                        public void onWaveFormDataCapture(
+                                Visualizer visualizer, byte[] waveform, int samplingRate) {
+                            synchronized(lock) {
+                                if (visualizer == mVisualizer) {
+                                    if (mCaptureWaveform) {
+                                        mWaveform = waveform;
+                                        lock.notify();
+                                    }
+                                }
+                            }
+                        }
+
+                        public void onFftDataCapture(
+                                Visualizer visualizer, byte[] fft, int samplingRate) {
+                            synchronized(lock) {
+                                if (visualizer == mVisualizer) {
+                                    if (mCaptureFft) {
+                                        mFft = fft;
+                                        lock.notify();
+                                    }
+                                }
+                            }
+                        }
+                    },
+                    10000,
+                    true,
+                    true);
+                }
+
+                synchronized(lock) {
+                    mInitialized = true;
+                    lock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+    /*
+     * Terminates the listener looper thread.
+     */
+    private void terminateListenerLooper() {
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+    }
+
+}
+
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/power/MediaPlayerPowerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/power/MediaPlayerPowerTest.java
new file mode 100644
index 0000000..9e91740
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/power/MediaPlayerPowerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.mediaframeworktest.power;
+
+import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaNames;
+import android.media.MediaPlayer;
+import android.os.Environment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.io.File;
+
+/**
+ * Junit / Instrumentation test case for the power measurment the media player
+ */
+public class MediaPlayerPowerTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
+    private String TAG = "MediaPlayerPowerTest";
+    private String MP3_POWERTEST =
+            Environment.getExternalStorageDirectory().toString() + "/power_sample_mp3.mp3";
+    private String MP3_STREAM = "http://75.17.48.204:10088/power_media/power_sample_mp3.mp3";
+    private String OGG_STREAM = "http://75.17.48.204:10088/power_media/power_sample_ogg.mp3";
+    private String AAC_STREAM = "http://75.17.48.204:10088/power_media/power_sample_aac.mp3";
+
+    public MediaPlayerPowerTest() {
+        super("com.android.mediaframeworktest", MediaFrameworkTest.class);
+    }
+
+    protected void setUp() throws Exception {
+        getActivity();
+        super.setUp();
+
+    }
+
+    public void audioPlayback(String filePath) {
+        try {
+            MediaPlayer mp = new MediaPlayer();
+            mp.setDataSource(filePath);
+            mp.prepare();
+            mp.start();
+            Thread.sleep(200000);
+            mp.stop();
+            mp.release();
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+            assertTrue("MP3 Playback", false);
+        }
+    }
+
+    // A very simple test case which start the audio player.
+    // Power measurment will be done in other application.
+    public void testPowerLocalMP3Playback() throws Exception {
+        audioPlayback(MP3_POWERTEST);
+    }
+
+    public void testPowerStreamMP3Playback() throws Exception {
+        audioPlayback(MP3_STREAM);
+    }
+
+    public void testPowerStreamOGGPlayback() throws Exception {
+        audioPlayback(OGG_STREAM);
+    }
+
+    public void testPowerStreamAACPlayback() throws Exception {
+        audioPlayback(AAC_STREAM);
+    }
+}
diff --git a/native/android/Android.mk b/native/android/Android.mk
index 950a1e9..bd2b27a 100644
--- a/native/android/Android.mk
+++ b/native/android/Android.mk
@@ -7,6 +7,7 @@
 #
 LOCAL_SRC_FILES:= \
     asset_manager.cpp \
+    configuration.cpp \
     input.cpp \
     looper.cpp \
     native_activity.cpp \
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 36c381e..3f7c1b6 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -17,7 +17,7 @@
 #define LOG_TAG "NAsset"
 #include <utils/Log.h>
 
-#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
 #include <utils/AssetManager.h>
 #include <utils/AssetDir.h>
 #include <utils/Asset.h>
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
new file mode 100644
index 0000000..d76164f
--- /dev/null
+++ b/native/android/configuration.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Configuration"
+#include <utils/Log.h>
+
+#include <utils/AssetManager.h>
+
+#include <android_runtime/android_content_res_Configuration.h>
+
+using namespace android;
+
+AConfiguration* AConfiguration_new() {
+    AConfiguration* config = new AConfiguration;
+    memset(config, 0, sizeof(AConfiguration));
+    return config;
+}
+
+void AConfiguration_delete(AConfiguration* config) {
+    delete config;
+}
+
+void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
+    ((AssetManager*)am)->getConfiguration(out);
+}
+
+void AConfiguration_copy(AConfiguration* dest, AConfiguration* src) {
+    *dest = *src;
+}
+
+int32_t AConfiguration_getMcc(AConfiguration* config) {
+    return config->mcc;
+}
+
+int32_t AConfiguration_getMnc(AConfiguration* config) {
+    return config->mnc;
+}
+
+void AConfiguration_getLanguage(AConfiguration* config, char* outLanguage) {
+    outLanguage[0] = config->language[0];
+    outLanguage[1] = config->language[1];
+}
+
+void AConfiguration_getCountry(AConfiguration* config, char* outCountry) {
+    outCountry[0] = config->country[0];
+    outCountry[1] = config->country[1];
+}
+
+int32_t AConfiguration_getOrientation(AConfiguration* config) {
+    return config->orientation;
+}
+
+int32_t AConfiguration_getTouchscreen(AConfiguration* config) {
+    return config->touchscreen;
+}
+
+int32_t AConfiguration_getDensity(AConfiguration* config) {
+    return config->density;
+}
+
+int32_t AConfiguration_getKeyboard(AConfiguration* config) {
+    return config->keyboard;
+}
+
+int32_t AConfiguration_getNavigation(AConfiguration* config) {
+    return config->navigation;
+}
+
+int32_t AConfiguration_getKeysHidden(AConfiguration* config) {
+    return config->inputFlags&ResTable_config::MASK_KEYSHIDDEN;
+}
+
+int32_t AConfiguration_getNavHidden(AConfiguration* config) {
+    return (config->inputFlags&ResTable_config::MASK_NAVHIDDEN)
+            >> ResTable_config::SHIFT_NAVHIDDEN;
+}
+
+int32_t AConfiguration_getSdkVersion(AConfiguration* config) {
+    return config->sdkVersion;
+}
+
+int32_t AConfiguration_getScreenSize(AConfiguration* config) {
+    return config->screenLayout&ResTable_config::MASK_SCREENSIZE;
+}
+
+int32_t AConfiguration_getScreenLong(AConfiguration* config) {
+    return (config->screenLayout&ResTable_config::MASK_SCREENLONG)
+            >> ResTable_config::SHIFT_SCREENLONG;
+}
+
+int32_t AConfiguration_getUiModeType(AConfiguration* config) {
+    return config->uiMode&ResTable_config::MASK_UI_MODE_TYPE;
+}
+
+int32_t AConfiguration_getUiModeNight(AConfiguration* config) {
+    return (config->uiMode&ResTable_config::MASK_UI_MODE_NIGHT)
+            >> ResTable_config::SHIFT_UI_MODE_NIGHT;
+
+}
+
+// ----------------------------------------------------------------------
+
+void AConfiguration_setMcc(AConfiguration* config, int32_t mcc) {
+    config->mcc = mcc;
+}
+
+void AConfiguration_setMnc(AConfiguration* config, int32_t mnc) {
+    config->mnc = mnc;
+}
+
+void AConfiguration_setLanguage(AConfiguration* config, const char* language) {
+    config->language[0] = language[0];
+    config->language[1] = language[1];
+}
+
+void AConfiguration_setCountry(AConfiguration* config, const char* country) {
+    config->country[0] = country[0];
+    config->country[1] = country[1];
+}
+
+void AConfiguration_setOrientation(AConfiguration* config, int32_t orientation) {
+    config->orientation = orientation;
+}
+
+void AConfiguration_setTouchscreen(AConfiguration* config, int32_t touchscreen) {
+    config->touchscreen = touchscreen;
+}
+
+void AConfiguration_setDensity(AConfiguration* config, int32_t density) {
+    config->density = density;
+}
+
+void AConfiguration_setKeyboard(AConfiguration* config, int32_t keyboard) {
+    config->keyboard = keyboard;
+}
+
+void AConfiguration_setNavigation(AConfiguration* config, int32_t navigation) {
+    config->navigation = navigation;
+}
+
+void AConfiguration_setKeysHidden(AConfiguration* config, int32_t keysHidden) {
+    config->inputFlags = (config->inputFlags&~ResTable_config::MASK_KEYSHIDDEN)
+            | (keysHidden&ResTable_config::MASK_KEYSHIDDEN);
+}
+
+void AConfiguration_setNavHidden(AConfiguration* config, int32_t navHidden) {
+    config->inputFlags = (config->inputFlags&~ResTable_config::MASK_NAVHIDDEN)
+            | ((navHidden<<ResTable_config::SHIFT_NAVHIDDEN)&ResTable_config::MASK_NAVHIDDEN);
+}
+
+void AConfiguration_setSdkVersion(AConfiguration* config, int32_t sdkVersion) {
+    config->sdkVersion = sdkVersion;
+}
+
+void AConfiguration_setScreenSize(AConfiguration* config, int32_t screenSize) {
+    config->screenLayout = (config->screenLayout&~ResTable_config::MASK_SCREENSIZE)
+            | (screenSize&ResTable_config::MASK_SCREENSIZE);
+}
+
+void AConfiguration_setScreenLong(AConfiguration* config, int32_t screenLong) {
+    config->screenLayout = (config->screenLayout&~ResTable_config::MASK_SCREENLONG)
+            | ((screenLong<<ResTable_config::SHIFT_SCREENLONG)&ResTable_config::MASK_SCREENLONG);
+}
+
+void AConfiguration_setUiModeType(AConfiguration* config, int32_t uiModeType) {
+    config->uiMode = (config->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+            | (uiModeType&ResTable_config::MASK_UI_MODE_TYPE);
+}
+
+void AConfiguration_setUiModeNight(AConfiguration* config, int32_t uiModeNight) {
+    config->uiMode = (config->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
+            | ((uiModeNight<<ResTable_config::SHIFT_UI_MODE_NIGHT)&ResTable_config::MASK_UI_MODE_NIGHT);
+
+}
+
+// ----------------------------------------------------------------------
+
+int32_t AConfiguration_diff(AConfiguration* config1, AConfiguration* config2) {
+    return (config1->diff(*config2));
+}
+
+int32_t AConfiguration_match(AConfiguration* base, AConfiguration* requested) {
+    return base->match(*requested);
+}
+
+int32_t AConfiguration_isBetterThan(AConfiguration* base, AConfiguration* test,
+        AConfiguration* requested) {
+    return base->isBetterThan(*test, requested);
+}
diff --git a/native/copy-to-ndk.sh b/native/copy-to-ndk.sh
new file mode 100644
index 0000000..4f5a16a
--- /dev/null
+++ b/native/copy-to-ndk.sh
@@ -0,0 +1,55 @@
+# Take care of copying current header files over to the correct
+# location in the NDK.
+
+copyndkheaders() {
+    local CURR_PLATFORM=android-9
+    local ALL_PLATFORMS="$CURR_PLATFORM android-8 android-5 android-4 android-3"
+
+    local SRC_HEADERS=$ANDROID_BUILD_TOP/frameworks/base/native/include/android
+    local NDK_PLATFORMS=$ANDROID_BUILD_TOP/development/ndk/platforms
+    local DST_HEADERS=$NDK_PLATFORMS/$CURR_PLATFORM
+
+    local SRC_LIB_ANDROID=$ANDROID_PRODUCT_OUT/system/lib/libandroid.so
+    local DST_LIB_ANDROID=$NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/usr/lib/libandroid.so
+
+    local didsomething=""
+
+    #echo "SRC_HEADERS: $SRC_HEADERS"
+
+    for i in $(cd $SRC_HEADERS; ls *.h); do
+        local src=$SRC_HEADERS/$i
+        local changed=""
+        for j in $ALL_PLATFORMS; do
+            local dst=$NDK_PLATFORMS/$j/arch-arm/usr/include/android/$i
+            if [ "$changed" == "" -a -e $dst ]; then
+                #echo "Exists: $dst"
+                if diff $src $dst >/dev/null; then
+                    echo "$i: has not changed from $j" >/dev/null
+                    changed="false"
+                else
+                    changed="true"
+                    echo "$i: has changed from $j" >/dev/null
+                fi
+            fi
+        done
+        if [ "$changed" == "true" -o "$changed" == "" ]; then
+            echo "Updating: $i"
+            cp $src $NDK_PLATFORMS/$CURR_PLATFORM/arch-arm/usr/include/android/$i
+            didsomething="true"
+        fi
+    done
+
+    if diff $SRC_LIB_ANDROID $DST_LIB_ANDROID >/dev/null; then
+        echo "libandroid.so: has not changed" >/dev/null
+    else
+        echo "Updating: $DST_LIB_ANDROID"
+        cp $SRC_LIB_ANDROID $DST_LIB_ANDROID
+        didsomething="true"
+    fi
+    if [ "$didsomething" != "" ]; then
+        echo "Headers changed...  rebuilding platforms."
+        sh $ANDROID_BUILD_TOP/ndk/build/tools/build-platforms.sh
+    fi
+}
+
+copyndkheaders
diff --git a/native/include/android/asset_manager.h b/native/include/android/asset_manager.h
index 89989f8..4fa0ef3 100644
--- a/native/include/android/asset_manager.h
+++ b/native/include/android/asset_manager.h
@@ -18,8 +18,6 @@
 #ifndef ANDROID_ASSET_MANAGER_H
 #define ANDROID_ASSET_MANAGER_H
 
-#include <jni.h>
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -43,14 +41,6 @@
 
 
 /**
- * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager
- * object.  Note that the caller is responsible for obtaining and holding a VM reference
- * to the jobject to prevent its being garbage collected while the native object is
- * in use.
- */
-AAssetManager* AAssetManager_fromJava(JNIEnv* env, jobject assetManager);
-
-/**
  * Open the named directory within the asset hierarchy.  The directory can then
  * be inspected with the AAssetDir functions.  To open the top-level directory,
  * pass in "" as the dirName.
diff --git a/native/include/android/asset_manager_jni.h b/native/include/android/asset_manager_jni.h
new file mode 100644
index 0000000..aec2d3c
--- /dev/null
+++ b/native/include/android/asset_manager_jni.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_ASSET_MANAGER_JNI_H
+#define ANDROID_ASSET_MANAGER_JNI_H
+
+#include <android/asset_manager.h>
+#include <jni.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Given a Dalvik AssetManager object, obtain the corresponding native AAssetManager
+ * object.  Note that the caller is responsible for obtaining and holding a VM reference
+ * to the jobject to prevent its being garbage collected while the native object is
+ * in use.
+ */
+AAssetManager* AAssetManager_fromJava(JNIEnv* env, jobject assetManager);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif      // ANDROID_ASSET_MANAGER_JNI_H
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
new file mode 100644
index 0000000..79b9b1e
--- /dev/null
+++ b/native/include/android/configuration.h
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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_CONFIGURATION_H
+#define ANDROID_CONFIGURATION_H
+
+#include <android/asset_manager.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AConfiguration;
+typedef struct AConfiguration AConfiguration;
+
+enum {
+    ACONFIGURATION_ORIENTATION_ANY  = 0x0000,
+    ACONFIGURATION_ORIENTATION_PORT = 0x0001,
+    ACONFIGURATION_ORIENTATION_LAND = 0x0002,
+    ACONFIGURATION_ORIENTATION_SQUARE = 0x0003,
+
+    ACONFIGURATION_TOUCHSCREEN_ANY  = 0x0000,
+    ACONFIGURATION_TOUCHSCREEN_NOTOUCH  = 0x0001,
+    ACONFIGURATION_TOUCHSCREEN_STYLUS  = 0x0002,
+    ACONFIGURATION_TOUCHSCREEN_FINGER  = 0x0003,
+
+    ACONFIGURATION_DENSITY_DEFAULT = 0,
+    ACONFIGURATION_DENSITY_LOW = 120,
+    ACONFIGURATION_DENSITY_MEDIUM = 160,
+    ACONFIGURATION_DENSITY_HIGH = 240,
+    ACONFIGURATION_DENSITY_NONE = 0xffff,
+
+    ACONFIGURATION_KEYBOARD_ANY  = 0x0000,
+    ACONFIGURATION_KEYBOARD_NOKEYS  = 0x0001,
+    ACONFIGURATION_KEYBOARD_QWERTY  = 0x0002,
+    ACONFIGURATION_KEYBOARD_12KEY  = 0x0003,
+
+    ACONFIGURATION_NAVIGATION_ANY  = 0x0000,
+    ACONFIGURATION_NAVIGATION_NONAV  = 0x0001,
+    ACONFIGURATION_NAVIGATION_DPAD  = 0x0002,
+    ACONFIGURATION_NAVIGATION_TRACKBALL  = 0x0003,
+    ACONFIGURATION_NAVIGATION_WHEEL  = 0x0004,
+
+    ACONFIGURATION_KEYSHIDDEN_ANY = 0x0000,
+    ACONFIGURATION_KEYSHIDDEN_NO = 0x0001,
+    ACONFIGURATION_KEYSHIDDEN_YES = 0x0002,
+    ACONFIGURATION_KEYSHIDDEN_SOFT = 0x0003,
+
+    ACONFIGURATION_NAVHIDDEN_ANY = 0x0000,
+    ACONFIGURATION_NAVHIDDEN_NO = 0x0001,
+    ACONFIGURATION_NAVHIDDEN_YES = 0x0002,
+
+    ACONFIGURATION_SCREENSIZE_ANY  = 0x00,
+    ACONFIGURATION_SCREENSIZE_SMALL = 0x01,
+    ACONFIGURATION_SCREENSIZE_NORMAL = 0x02,
+    ACONFIGURATION_SCREENSIZE_LARGE = 0x03,
+    ACONFIGURATION_SCREENSIZE_XLARGE = 0x04,
+
+    ACONFIGURATION_SCREENLONG_ANY = 0x00,
+    ACONFIGURATION_SCREENLONG_NO = 0x1,
+    ACONFIGURATION_SCREENLONG_YES = 0x2,
+
+    ACONFIGURATION_UI_MODE_TYPE_ANY = 0x00,
+    ACONFIGURATION_UI_MODE_TYPE_NORMAL = 0x01,
+    ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02,
+    ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
+
+    ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
+    ACONFIGURATION_UI_MODE_NIGHT_NO = 0x10,
+    ACONFIGURATION_UI_MODE_NIGHT_YES = 0x20,
+
+    ACONFIGURATION_MCC = 0x0001,
+    ACONFIGURATION_MNC = 0x0002,
+    ACONFIGURATION_LOCALE = 0x0004,
+    ACONFIGURATION_TOUCHSCREEN = 0x0008,
+    ACONFIGURATION_KEYBOARD = 0x0010,
+    ACONFIGURATION_KEYBOARD_HIDDEN = 0x0020,
+    ACONFIGURATION_NAVIGATION = 0x0040,
+    ACONFIGURATION_ORIENTATION = 0x0080,
+    ACONFIGURATION_DENSITY = 0x0100,
+    ACONFIGURATION_SCREEN_SIZE = 0x0200,
+    ACONFIGURATION_VERSION = 0x0400,
+    ACONFIGURATION_SCREEN_LAYOUT = 0x0800,
+    ACONFIGURATION_UI_MODE = 0x1000,
+};
+
+/**
+ * Create a new AConfiguration, initialized with no values set.
+ */
+AConfiguration* AConfiguration_new();
+
+/**
+ * Free an AConfiguration that was previously created with
+ * AConfiguration_new().
+ */
+void AConfiguration_delete(AConfiguration* config);
+
+/**
+ * Create and return a new AConfiguration based on the current configuration in
+ * use in the given AssetManager.
+ */
+void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am);
+
+/**
+ * Copy the contents of 'src' to 'dest'.
+ */
+void AConfiguration_copy(AConfiguration* dest, AConfiguration* src);
+
+/**
+ * Return the current MCC set in the configuration.  0 if not set.
+ */
+int32_t AConfiguration_getMcc(AConfiguration* config);
+
+/**
+ * Set the current MCC in the configuration.  0 to clear.
+ */
+void AConfiguration_setMcc(AConfiguration* config, int32_t mcc);
+
+/**
+ * Return the current MNC set in the configuration.  0 if not set.
+ */
+int32_t AConfiguration_getMnc(AConfiguration* config);
+
+/**
+ * Set the current MNC in the configuration.  0 to clear.
+ */
+void AConfiguration_setMnc(AConfiguration* config, int32_t mnc);
+
+/**
+ * Return the current language code set in the configuration.  The output will
+ * be filled with an array of two characters.  They are not 0-terminated.  If
+ * a language is not set, they will be 0.
+ */
+void AConfiguration_getLanguage(AConfiguration* config, char* outLanguage);
+
+/**
+ * Set the current language code in the configuration, from the first two
+ * characters in the string.
+ */
+void AConfiguration_setLanguage(AConfiguration* config, const char* language);
+
+/**
+ * Return the current country code set in the configuration.  The output will
+ * be filled with an array of two characters.  They are not 0-terminated.  If
+ * a country is not set, they will be 0.
+ */
+void AConfiguration_getCountry(AConfiguration* config, char* outCountry);
+
+/**
+ * Set the current country code in the configuration, from the first two
+ * characters in the string.
+ */
+void AConfiguration_setCountry(AConfiguration* config, const char* country);
+
+/**
+ * Return the current ACONFIGURATION_ORIENTATION_* set in the configuration.
+ */
+int32_t AConfiguration_getOrientation(AConfiguration* config);
+
+/**
+ * Set the current orientation in the configuration.
+ */
+void AConfiguration_setOrientation(AConfiguration* config, int32_t orientation);
+
+/**
+ * Return the current ACONFIGURATION_TOUCHSCREEN_* set in the configuration.
+ */
+int32_t AConfiguration_getTouchscreen(AConfiguration* config);
+
+/**
+ * Set the current touchscreen in the configuration.
+ */
+void AConfiguration_setTouchscreen(AConfiguration* config, int32_t touchscreen);
+
+/**
+ * Return the current ACONFIGURATION_DENSITY_* set in the configuration.
+ */
+int32_t AConfiguration_getDensity(AConfiguration* config);
+
+/**
+ * Set the current density in the configuration.
+ */
+void AConfiguration_setDensity(AConfiguration* config, int32_t density);
+
+/**
+ * Return the current ACONFIGURATION_KEYBOARD_* set in the configuration.
+ */
+int32_t AConfiguration_getKeyboard(AConfiguration* config);
+
+/**
+ * Set the current keyboard in the configuration.
+ */
+void AConfiguration_setKeyboard(AConfiguration* config, int32_t keyboard);
+
+/**
+ * Return the current ACONFIGURATION_NAVIGATION_* set in the configuration.
+ */
+int32_t AConfiguration_getNavigation(AConfiguration* config);
+
+/**
+ * Set the current navigation in the configuration.
+ */
+void AConfiguration_setNavigation(AConfiguration* config, int32_t navigation);
+
+/**
+ * Return the current ACONFIGURATION_KEYSHIDDEN_* set in the configuration.
+ */
+int32_t AConfiguration_getKeysHidden(AConfiguration* config);
+
+/**
+ * Set the current keys hidden in the configuration.
+ */
+void AConfiguration_setKeysHidden(AConfiguration* config, int32_t keysHidden);
+
+/**
+ * Return the current ACONFIGURATION_NAVHIDDEN_* set in the configuration.
+ */
+int32_t AConfiguration_getNavHidden(AConfiguration* config);
+
+/**
+ * Set the current nav hidden in the configuration.
+ */
+void AConfiguration_setNavHidden(AConfiguration* config, int32_t navHidden);
+
+/**
+ * Return the current SDK (API) version set in the configuration.
+ */
+int32_t AConfiguration_getSdkVersion(AConfiguration* config);
+
+/**
+ * Set the current SDK version in the configuration.
+ */
+void AConfiguration_setSdkVersion(AConfiguration* config, int32_t sdkVersion);
+
+/**
+ * Return the current ACONFIGURATION_SCREENSIZE_* set in the configuration.
+ */
+int32_t AConfiguration_getScreenSize(AConfiguration* config);
+
+/**
+ * Set the current screen size in the configuration.
+ */
+void AConfiguration_setScreenSize(AConfiguration* config, int32_t screenSize);
+
+/**
+ * Return the current ACONFIGURATION_SCREENLONG_* set in the configuration.
+ */
+int32_t AConfiguration_getScreenLong(AConfiguration* config);
+
+/**
+ * Set the current screen long in the configuration.
+ */
+void AConfiguration_setScreenLong(AConfiguration* config, int32_t screenLong);
+
+/**
+ * Return the current ACONFIGURATION_UI_MODE_TYPE_* set in the configuration.
+ */
+int32_t AConfiguration_getUiModeType(AConfiguration* config);
+
+/**
+ * Set the current UI mode type in the configuration.
+ */
+void AConfiguration_setUiModeType(AConfiguration* config, int32_t uiModeType);
+
+/**
+ * Return the current ACONFIGURATION_UI_MODE_NIGHT_* set in the configuration.
+ */
+int32_t AConfiguration_getUiModeNight(AConfiguration* config);
+
+/**
+ * Set the current UI mode night in the configuration.
+ */
+void AConfiguration_setUiModeNight(AConfiguration* config, int32_t uiModeNight);
+
+/**
+ * Perform a diff between two configurations.  Returns a bit mask of
+ * ACONFIGURATION_* constants, each bit set meaning that configuration element
+ * is different between them.
+ */
+int32_t AConfiguration_diff(AConfiguration* config1, AConfiguration* config2);
+
+/**
+ * Determine whether 'base' is a valid configuration for use within the
+ * environment 'requested'.  Returns 0 if there are any values in 'base'
+ * that conflict with 'requested'.  Returns 1 if it does not conflict.
+ */
+int32_t AConfiguration_match(AConfiguration* base, AConfiguration* requested);
+
+/**
+ * Determine whether the configuration in 'test' is better than the existing
+ * configuration in 'base'.  If 'requested' is non-NULL, this decision is based
+ * on the overall configuration given there.  If it is NULL, this decision is
+ * simply based on which configuration is more specific.  Returns non-0 if
+ * 'test' is better than 'base'.
+ *
+ * This assumes you have already filtered the configurations with
+ * AConfiguration_match().
+ */
+int32_t AConfiguration_isBetterThan(AConfiguration* base, AConfiguration* test,
+        AConfiguration* requested);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // ANDROID_CONFIGURATION_H
diff --git a/native/include/android/native_activity.h b/native/include/android/native_activity.h
index ee4204d..d74e1ce 100644
--- a/native/include/android/native_activity.h
+++ b/native/include/android/native_activity.h
@@ -197,6 +197,12 @@
     void (*onContentRectChanged)(ANativeActivity* activity, const ARect* rect);
 
     /**
+     * The current device AConfiguration has changed.  The new configuration can
+     * be retrieved from assetManager.
+     */
+    void (*onConfigurationChanged)(ANativeActivity* activity);
+
+    /**
      * The system is running low on memory.  Use this callback to release
      * resources you do not need, to help the system avoid killing more
      * important processes.
@@ -208,7 +214,9 @@
  * This is the function that must be in the native code to instantiate the
  * application's native activity.  It is called with the activity instance (see
  * above); if the code is being instantiated from a previously saved instance,
- * the savedState will be non-NULL and point to the saved data.
+ * the savedState will be non-NULL and point to the saved data.  You must make
+ * any copy of this data you need -- it will be released after you return from
+ * this function.
  */
 typedef void ANativeActivity_createFunc(ANativeActivity* activity,
         void* savedState, size_t savedStateSize);
diff --git a/native/include/android/native_window.h b/native/include/android/native_window.h
index 7599d7e..ad03d0e 100644
--- a/native/include/android/native_window.h
+++ b/native/include/android/native_window.h
@@ -36,12 +36,23 @@
 typedef struct ANativeWindow ANativeWindow;
 
 typedef struct ANativeWindow_Buffer {
+    // The number of pixels that are show horizontally.
     int32_t width;
+
+    // The number of pixels that are shown vertically.
     int32_t height;
+
+    // The number of *pixels* that a line in the buffer takes in
+    // memory.  This may be >= width.
     int32_t stride;
+
+    // The format of the buffer.  One of WINDOW_FORMAT_*
     int32_t format;
+
+    // The actual bits.
     void* bits;
     
+    // Do not touch.
     uint32_t reserved[6];
 } ANativeWindow_Buffer;
 
diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp
index 23837ef..dcf8735c 100644
--- a/opengl/libs/EGL/getProcAddress.cpp
+++ b/opengl/libs/EGL/getProcAddress.cpp
@@ -27,9 +27,12 @@
 // ----------------------------------------------------------------------------
 
 #undef API_ENTRY
-#undef CALL_GL_API
+#undef CALL_GL_EXTENSION_API
 #undef GL_EXTENSION
 #undef GL_EXTENSION_NAME
+#undef GL_EXTENSION_ARRAY
+#undef GL_EXTENSION_LIST
+#undef GET_TLS
 
 #if defined(__arm__)
 
@@ -60,7 +63,7 @@
             :                                                   \
             );
 
-    #define GL_EXTENSION_NAME(_n)  __glExtFwd##_n
+    #define GL_EXTENSION_NAME(_n)   __glExtFwd##_n
 
     #define GL_EXTENSION(_n)                         \
         void API_ENTRY(GL_EXTENSION_NAME(_n))() {    \
@@ -78,97 +81,40 @@
 
 #endif
 
-GL_EXTENSION(0)
-GL_EXTENSION(1)
-GL_EXTENSION(2)
-GL_EXTENSION(3)
-GL_EXTENSION(4)
-GL_EXTENSION(5)
-GL_EXTENSION(6)
-GL_EXTENSION(7)
-GL_EXTENSION(8)
-GL_EXTENSION(9)
-GL_EXTENSION(10)
-GL_EXTENSION(11)
-GL_EXTENSION(12)
-GL_EXTENSION(13)
-GL_EXTENSION(14)
-GL_EXTENSION(15)
+#define GL_EXTENSION_LIST(name) \
+        name(0)   name(1)   name(2)   name(3)   \
+        name(4)   name(5)   name(6)   name(7)   \
+        name(8)   name(9)   name(10)  name(11)  \
+        name(12)  name(13)  name(14)  name(15)  \
+        name(16)  name(17)  name(18)  name(19)  \
+        name(20)  name(21)  name(22)  name(23)  \
+        name(24)  name(25)  name(26)  name(27)  \
+        name(28)  name(29)  name(30)  name(31)  \
+        name(32)  name(33)  name(34)  name(35)  \
+        name(36)  name(37)  name(38)  name(39)  \
+        name(40)  name(41)  name(42)  name(43)  \
+        name(44)  name(45)  name(46)  name(47)  \
+        name(48)  name(49)  name(50)  name(51)  \
+        name(52)  name(53)  name(54)  name(55)  \
+        name(56)  name(57)  name(58)  name(59)  \
+        name(60)  name(61)  name(62)  name(63)
 
-GL_EXTENSION(16)
-GL_EXTENSION(17)
-GL_EXTENSION(18)
-GL_EXTENSION(19)
-GL_EXTENSION(20)
-GL_EXTENSION(21)
-GL_EXTENSION(22)
-GL_EXTENSION(23)
-GL_EXTENSION(24)
-GL_EXTENSION(25)
-GL_EXTENSION(26)
-GL_EXTENSION(27)
-GL_EXTENSION(28)
-GL_EXTENSION(29)
-GL_EXTENSION(30)
-GL_EXTENSION(31)
 
-GL_EXTENSION(32)
-GL_EXTENSION(33)
-GL_EXTENSION(34)
-GL_EXTENSION(35)
-GL_EXTENSION(36)
-GL_EXTENSION(37)
-GL_EXTENSION(38)
-GL_EXTENSION(39)
-GL_EXTENSION(40)
-GL_EXTENSION(41)
-GL_EXTENSION(42)
-GL_EXTENSION(43)
-GL_EXTENSION(44)
-GL_EXTENSION(45)
-GL_EXTENSION(46)
-GL_EXTENSION(47)
+GL_EXTENSION_LIST( GL_EXTENSION )
 
-GL_EXTENSION(48)
-GL_EXTENSION(49)
-GL_EXTENSION(50)
-GL_EXTENSION(51)
-GL_EXTENSION(52)
-GL_EXTENSION(53)
-GL_EXTENSION(54)
-GL_EXTENSION(55)
-GL_EXTENSION(56)
-GL_EXTENSION(57)
-GL_EXTENSION(58)
-GL_EXTENSION(59)
-GL_EXTENSION(60)
-GL_EXTENSION(61)
-GL_EXTENSION(62)
-GL_EXTENSION(63)
+#define GL_EXTENSION_ARRAY(_n)  GL_EXTENSION_NAME(_n),
 
 extern const __eglMustCastToProperFunctionPointerType gExtensionForwarders[MAX_NUMBER_OF_GL_EXTENSIONS] = {
-     GL_EXTENSION_NAME(0),  GL_EXTENSION_NAME(1),  GL_EXTENSION_NAME(2),  GL_EXTENSION_NAME(3),
-     GL_EXTENSION_NAME(4),  GL_EXTENSION_NAME(5),  GL_EXTENSION_NAME(6),  GL_EXTENSION_NAME(7),
-     GL_EXTENSION_NAME(8),  GL_EXTENSION_NAME(9),  GL_EXTENSION_NAME(10), GL_EXTENSION_NAME(11),
-     GL_EXTENSION_NAME(12), GL_EXTENSION_NAME(13), GL_EXTENSION_NAME(14), GL_EXTENSION_NAME(15),
-     GL_EXTENSION_NAME(16), GL_EXTENSION_NAME(17), GL_EXTENSION_NAME(18), GL_EXTENSION_NAME(19),
-     GL_EXTENSION_NAME(20), GL_EXTENSION_NAME(21), GL_EXTENSION_NAME(22), GL_EXTENSION_NAME(23),
-     GL_EXTENSION_NAME(24), GL_EXTENSION_NAME(25), GL_EXTENSION_NAME(26), GL_EXTENSION_NAME(27),
-     GL_EXTENSION_NAME(28), GL_EXTENSION_NAME(29), GL_EXTENSION_NAME(30), GL_EXTENSION_NAME(31),
-     GL_EXTENSION_NAME(32), GL_EXTENSION_NAME(33), GL_EXTENSION_NAME(34), GL_EXTENSION_NAME(35),
-     GL_EXTENSION_NAME(36), GL_EXTENSION_NAME(37), GL_EXTENSION_NAME(38), GL_EXTENSION_NAME(39),
-     GL_EXTENSION_NAME(40), GL_EXTENSION_NAME(41), GL_EXTENSION_NAME(42), GL_EXTENSION_NAME(43),
-     GL_EXTENSION_NAME(44), GL_EXTENSION_NAME(45), GL_EXTENSION_NAME(46), GL_EXTENSION_NAME(47),
-     GL_EXTENSION_NAME(48), GL_EXTENSION_NAME(49), GL_EXTENSION_NAME(50), GL_EXTENSION_NAME(51),
-     GL_EXTENSION_NAME(52), GL_EXTENSION_NAME(53), GL_EXTENSION_NAME(54), GL_EXTENSION_NAME(55),
-     GL_EXTENSION_NAME(56), GL_EXTENSION_NAME(57), GL_EXTENSION_NAME(58), GL_EXTENSION_NAME(59),
-     GL_EXTENSION_NAME(60), GL_EXTENSION_NAME(61), GL_EXTENSION_NAME(62), GL_EXTENSION_NAME(63)
+        GL_EXTENSION_LIST( GL_EXTENSION_ARRAY )
  };
 
+#undef GET_TLS
+#undef GL_EXTENSION_LIST
+#undef GL_EXTENSION_ARRAY
 #undef GL_EXTENSION_NAME
 #undef GL_EXTENSION
 #undef API_ENTRY
-#undef CALL_GL_API
+#undef CALL_GL_EXTENSION_API
 
 // ----------------------------------------------------------------------------
 }; // namespace android
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index 924737e..a12edf2 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -39,6 +39,8 @@
 #undef CALL_GL_API
 #undef CALL_GL_API_RETURN
 
+#define DEBUG_CALL_GL_API 0
+
 #if USE_FAST_TLS_KEY
 
     #ifdef HAVE_ARM_TLS_REGISTER
@@ -73,10 +75,24 @@
 
     #define API_ENTRY(_api) _api
 
+#if DEBUG_CALL_GL_API
+
     #define CALL_GL_API(_api, ...)                                       \
         gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl;  \
-        _c->_api(__VA_ARGS__)
-    
+        _c->_api(__VA_ARGS__); \
+        GLenum status = GL_NO_ERROR; \
+        while ((status = glGetError()) != GL_NO_ERROR) { \
+            LOGD("[" #_api "] 0x%x", status); \
+        }
+
+#else
+
+    #define CALL_GL_API(_api, ...)                                       \
+        gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl;  \
+        _c->_api(__VA_ARGS__);
+
+#endif
+
     #define CALL_GL_API_RETURN(_api, ...)                                \
         gl_hooks_t::gl_t const * const _c = &getGlThreadSpecific()->gl;  \
         return _c->_api(__VA_ARGS__)
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png
new file mode 100644
index 0000000..92fc7f8
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png
new file mode 100644
index 0000000..77f09ac
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/status_bar_expand_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png
new file mode 100644
index 0000000..4d19717
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_default.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png
new file mode 100644
index 0000000..830cbd5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/status_bar_expand_pressed.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable/status_bar_expand.xml b/packages/SystemUI/res/drawable/status_bar_expand.xml
new file mode 100644
index 0000000..f966920
--- /dev/null
+++ b/packages/SystemUI/res/drawable/status_bar_expand.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/status_bar_expand_pressed" />
+    <item android:drawable="@drawable/status_bar_expand_default" />
+</selector>
+
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml
index 9ae2c25..ffb1571 100644
--- a/packages/SystemUI/res/layout-xlarge/status_bar.xml
+++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml
@@ -39,13 +39,13 @@
             android:onClick="notificationIconsClicked"
             android:background="@drawable/status_bar_icon_tray"
             >
-            <view
+            <ImageView
                 class="com.android.systemui.statusbar.tablet.NotificationIconArea$MoreView"
-                android:id="@+id/more"
+                android:id="@+id/expand"
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
-                android:src="@drawable/stat_notify_more"
-                android:layout_marginLeft="10dip"
+                android:layout_height="wrap_content"
+                android:src="@drawable/status_bar_expand"
+                android:onClick="notificationIconsClicked"
                 />
             <view
                 class="com.android.systemui.statusbar.tablet.NotificationIconArea$IconLayout"
@@ -64,39 +64,47 @@
 
         </com.android.systemui.statusbar.tablet.NotificationIconArea>
 
-        <RelativeLayout android:id="@+id/systemInfo"
-            android:layout_width="200dip"
+        <LinearLayout android:id="@+id/ticker"
+            android:layout_width="300dip"
             android:layout_height="match_parent"
-            android:layout_centerHorizontal="true"
-            android:clickable="true"
-            android:onClick="systemInfoClicked"
+            android:paddingLeft="6dip"
+            android:animationCache="false"
+            android:layout_alignLeft="@id/notificationIcons"
+            android:layout_alignTop="@id/notificationIcons"
+            android:orientation="horizontal"
+            android:visibility="gone"
             >
-            <com.android.systemui.statusbar.Clock
-                android:id="@+id/clock"
-                android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
+            <ImageView android:id="@+id/tickerIcon"
                 android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerInParent="true"
-                android:singleLine="true"
-                android:textSize="16sp"
-                android:textStyle="bold"
-                android:padding="6dip"
+                android:layout_height="match_parent"
+                android:layout_marginRight="8dip"
                 />
-            <ImageView
-                android:layout_width="wrap_content"
+            <com.android.systemui.statusbar.TickerView android:id="@+id/tickerText"
+                android:layout_width="0dip"
+                android:layout_weight="1"
                 android:layout_height="wrap_content"
-                android:layout_toLeftOf="@id/clock"
-                android:layout_centerVertical="true"
-                android:src="@drawable/dots_empty"
-                />
-            <ImageView
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_toRightOf="@id/clock"
-                android:layout_centerVertical="true"
-                android:src="@drawable/dots_full"
-                />
-        </RelativeLayout>
+                android:paddingTop="2dip"
+                android:paddingRight="10dip">
+                <TextView
+                    android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    />
+                <TextView
+                    android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:singleLine="true"
+                    />
+            </com.android.systemui.statusbar.TickerView>
+        </LinearLayout>
+
+        <include layout="@layout/status_bar_center"
+            android:layout_width="100dip"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            />
 
         <com.android.systemui.statusbar.KeyButtonView android:id="@+id/back"
             android:layout_width="wrap_content"
@@ -125,60 +133,5 @@
             android:src="@drawable/status_bar_home"
             systemui:keyCode="3"
             />
-            
-<!--
-        
-    <LinearLayout android:id="@+id/ticker"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:paddingLeft="6dip"
-        android:animationCache="false"
-        android:orientation="horizontal" >
-        <ImageSwitcher android:id="@+id/tickerIcon"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:layout_marginRight="8dip"
-            >
-            <com.android.systemui.statusbar.AnimatedImageView
-                android:layout_width="25dip"
-                android:layout_height="25dip"
-                />
-            <com.android.systemui.statusbar.AnimatedImageView
-                android:layout_width="25dip"
-                android:layout_height="25dip"
-                />
-        </ImageSwitcher>
-        <com.android.systemui.statusbar.TickerView android:id="@+id/tickerText"
-            android:layout_width="0dip"
-            android:layout_weight="1"
-            android:layout_height="wrap_content"
-            android:paddingTop="2dip"
-            android:paddingRight="10dip">
-            <TextView
-                android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                />
-            <TextView
-                android:textAppearance="@*android:style/TextAppearance.StatusBar.Ticker"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:singleLine="true"
-                />
-        </com.android.systemui.statusbar.TickerView>
-    </LinearLayout>
-
-    <com.android.systemui.statusbar.DateView android:id="@+id/date"
-        android:textAppearance="@*android:style/TextAppearance.StatusBar.Icon"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:singleLine="true"
-        android:gravity="center_vertical|left"
-        android:paddingLeft="6px"
-        android:paddingRight="6px"
-        android:background="@drawable/status_bar_background"
-        />
--->
 </RelativeLayout>
 
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar_center.xml b/packages/SystemUI/res/layout-xlarge/status_bar_center.xml
new file mode 100644
index 0000000..c32e997
--- /dev/null
+++ b/packages/SystemUI/res/layout-xlarge/status_bar_center.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Center of status bar: System info display, system info panel trigger -->
+<RelativeLayout android:id="@+id/systemInfo"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
+    android:layout_width="100dip"
+    android:layout_height="wrap_content"
+    android:layout_centerInParent="true"
+    android:clickable="true"
+    android:onClick="systemInfoClicked"
+    >
+    <com.android.systemui.statusbar.Clock
+        style="@*android:style/TextAppearance.StatusBar.Icon"
+        android:id="@+id/clock"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:gravity="center"
+        android:textSize="16sp"
+        android:textStyle="bold"
+        android:padding="2dip"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_below="@id/clock"
+        android:src="@drawable/dots_empty"
+        />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_below="@id/clock"
+        android:src="@drawable/dots_full"
+        />
+</RelativeLayout>
+
+
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
index 84a718a..3489eec 100644
--- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
@@ -46,17 +46,23 @@
         android:background="@android:drawable/divider_horizontal_dark"
         />
 
-    <LinearLayout 
-        android:id="@+id/content"
-        android:layout_width="match_parent"
+    <ScrollView
+        android:id="@+id/notificationScroller"
         android:layout_height="wrap_content"
-        android:gravity="center_horizontal|bottom"
-        android:animationCache="false"
-        android:orientation="vertical"
-        android:background="@drawable/status_bar_background"
-        android:clickable="true"
-        android:focusable="true"
-        android:descendantFocusability="afterDescendants"
+        android:layout_width="match_parent"
         >
-    </LinearLayout>
+        <LinearLayout 
+            android:id="@+id/content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal|bottom"
+            android:animationCache="false"
+            android:orientation="vertical"
+            android:background="@drawable/status_bar_background"
+            android:clickable="true"
+            android:focusable="true"
+            android:descendantFocusability="afterDescendants"
+            >
+        </LinearLayout>
+    </ScrollView>
 </LinearLayout>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
index 5add6de..91b583b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PhoneStatusBarService.java
@@ -1086,12 +1086,12 @@
     }
 
     private class MyTicker extends Ticker {
-        MyTicker(Context context, StatusBarView sb) {
+        MyTicker(Context context, View sb) {
             super(context, sb);
         }
 
         @Override
-        void tickerStarting() {
+        public void tickerStarting() {
             mTicking = true;
             mIcons.setVisibility(View.GONE);
             mTickerView.setVisibility(View.VISIBLE);
@@ -1103,7 +1103,7 @@
         }
 
         @Override
-        void tickerDone() {
+        public void tickerDone() {
             mIcons.setVisibility(View.VISIBLE);
             mTickerView.setVisibility(View.GONE);
             mIcons.startAnimation(loadAnim(com.android.internal.R.anim.push_down_in, null));
@@ -1114,7 +1114,7 @@
             }
         }
 
-        void tickerHalting() {
+        public void tickerHalting() {
             mIcons.setVisibility(View.VISIBLE);
             mTickerView.setVisibility(View.GONE);
             mIcons.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java
index 07e8653..0aaa370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/Ticker.java
@@ -141,7 +141,7 @@
         }
     };
 
-    Ticker(Context context, StatusBarView sb) {
+    public Ticker(Context context, View sb) {
         mContext = context;
         mTickerView = sb.findViewById(R.id.ticker);
 
@@ -163,7 +163,7 @@
     }
 
 
-    void addEntry(StatusBarNotification n) {
+    public void addEntry(StatusBarNotification n) {
         int initialCount = mSegments.size();
 
         // If what's being displayed has the same text and icon, just drop it
@@ -212,7 +212,7 @@
         }
     }
 
-    void removeEntry(StatusBarNotification n) {
+    public void removeEntry(StatusBarNotification n) {
         for (int i=mSegments.size()-1; i>=0; i--) {
             Segment seg = mSegments.get(i);
             if (n.id == seg.notification.id && n.pkg.equals(seg.notification.pkg)) {
@@ -221,13 +221,13 @@
         }
     }
 
-    void halt() {
+    public void halt() {
         mHandler.removeCallbacks(mAdvanceTicker);
         mSegments.clear();
         tickerHalting();
     }
 
-    void reflowText() {
+    public void reflowText() {
         if (mSegments.size() > 0) {
             Segment seg = mSegments.get(0);
             CharSequence text = seg.getText();
@@ -266,8 +266,8 @@
         mHandler.postDelayed(mAdvanceTicker, TICKER_SEGMENT_DELAY);
     }
 
-    abstract void tickerStarting();
-    abstract void tickerDone();
-    abstract void tickerHalting();
+    public abstract void tickerStarting();
+    public abstract void tickerDone();
+    public abstract void tickerHalting();
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java
index 9749ae4..8140811 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/TickerView.java
@@ -34,5 +34,9 @@
         super.onSizeChanged(w, h, oldw, oldh);
         mTicker.reflowText();
     }
+
+    public void setTicker(Ticker t) {
+        mTicker = t;
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
index 3c7b130..7c7d74c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/NotificationIconArea.java
@@ -31,24 +31,16 @@
 public class NotificationIconArea extends LinearLayout {
     private static final String TAG = "NotificationIconArea";
 
-    MoreView mMoreView;
     IconLayout mIconLayout;
     DraggerView mDraggerView;
 
     public NotificationIconArea(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mMoreView = (MoreView) findViewById(R.id.more);
         mIconLayout = (IconLayout)findViewById(R.id.icons);
         mDraggerView = (DraggerView) findViewById(R.id.handle);
     }
 
-    static class MoreView extends ImageView {
-        public MoreView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-        }
-    }
-
     static class IconLayout extends LinearLayout {
         public IconLayout(Context context, AttributeSet attrs) {
             super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
index b1c4ee3..9946ada 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBarService.java
@@ -16,30 +16,34 @@
 
 package com.android.systemui.statusbar.tablet;
 
+import android.animation.Animator;
+import android.app.ActivityManagerNative;
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.app.Service;
+import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
+import android.graphics.Rect;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Gravity;
 import android.view.LayoutInflater;
-import android.widget.RemoteViews;
-import android.app.ActivityManagerNative;
-import android.app.PendingIntent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.graphics.Rect;
-import android.os.RemoteException;
 import android.view.WindowManagerImpl;
 import android.widget.Button;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+import android.widget.ScrollView;
+import android.widget.TextSwitcher;
 import android.widget.TextView;
 
 import com.android.internal.statusbar.StatusBarIcon;
@@ -53,13 +57,18 @@
     public static final boolean DEBUG = false;
     public static final String TAG = "TabletStatusBar";
 
-    View mStatusBarView;
-    NotificationIconArea mNotificationIconArea;
+
 
     int mIconSize;
 
     H mHandler = new H();
 
+    // tracking all current notifications
+    private NotificationData mNotns = new NotificationData();
+    
+    View mStatusBarView;
+    NotificationIconArea mNotificationIconArea;
+
     View mNotificationPanel;
     View mSystemPanel;
 
@@ -68,8 +77,14 @@
 
     NotificationIconArea.IconLayout mIconLayout;
 
-    private NotificationData mNotns = new NotificationData();
-    
+    KickerController mKicker;
+    View mKickerView;
+    boolean mTicking;
+    boolean mExpandedVisible;
+
+    // for disabling the status bar
+    int mDisabled = 0;
+
     protected void addPanelWindows() {
         mNotificationPanel = View.inflate(this, R.layout.sysbar_panel_notifications, null);
         mSystemPanel = View.inflate(this, R.layout.sysbar_panel_system, null);
@@ -86,8 +101,8 @@
                 ViewGroup.LayoutParams.WRAP_CONTENT,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                 PixelFormat.TRANSLUCENT);
@@ -102,8 +117,8 @@
                 200, // ViewGroup.LayoutParams.WRAP_CONTENT,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                 PixelFormat.TRANSLUCENT);
@@ -112,23 +127,11 @@
         lp.windowAnimations = com.android.internal.R.style.Animation_SlidingCard;
 
         WindowManagerImpl.getDefault().addView(mSystemPanel, lp);
-
-        // Lorem ipsum, Dolores
-        TextView tv = ((TextView) mSystemPanel.findViewById(R.id.systemPanelDummy));
-        if (tv != null) tv.setText("System status: great");
-
-        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
-        mPile.removeAllViews();
-
-        mClearButton = (TextView)mNotificationPanel.findViewById(R.id.clear_all_button);
-        mClearButton.setOnClickListener(mClearButtonListener);
     }
 
     @Override
     public void onCreate() {
         super.onCreate(); // will add the main bar view
-
-        addPanelWindows();
     }
 
     protected View makeStatusBarView() {
@@ -145,6 +148,24 @@
         // where the icons go
         mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(R.id.icons);
 
+        mKicker = new KickerController((Context)this, mStatusBarView);
+
+        // Add the windows
+        addPanelWindows();
+
+        // Lorem ipsum, Dolores
+        TextView tv = ((TextView) mSystemPanel.findViewById(R.id.systemPanelDummy));
+        if (tv != null) tv.setText("System status: great");
+
+        mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
+        mPile.removeAllViews();
+        
+        ScrollView scroller = (ScrollView)mPile.getParent();
+        scroller.setFillViewport(true);
+
+        mClearButton = (TextView)mNotificationPanel.findViewById(R.id.clear_all_button);
+        mClearButton.setOnClickListener(mClearButtonListener);
+
         return sb;
     }
 
@@ -162,10 +183,12 @@
                 case MSG_OPEN_NOTIFICATION_PANEL:
                     if (DEBUG) Slog.d(TAG, "opening notifications panel");
                     mNotificationPanel.setVisibility(View.VISIBLE);
+                    mExpandedVisible = true;
                     break;
                 case MSG_CLOSE_NOTIFICATION_PANEL:
                     if (DEBUG) Slog.d(TAG, "closing notifications panel");
                     mNotificationPanel.setVisibility(View.GONE);
+                    mExpandedVisible = false;
                     break;
                 case MSG_OPEN_SYSTEM_PANEL:
                     if (DEBUG) Slog.d(TAG, "opening system panel");
@@ -195,8 +218,7 @@
     public void addNotification(IBinder key, StatusBarNotification notification) {
         if (DEBUG) Slog.d(TAG, "addNotification(" + key + " -> " + notification + ")");
         addNotificationViews(key, notification);
-
-        // TODO: kicker; immersive mode
+        // tick()
     }
 
     public void updateNotification(IBinder key, StatusBarNotification notification) {
@@ -274,7 +296,127 @@
     }
 
     public void disable(int state) {
-        // TODO
+        /*
+        final int old = mDisabled;
+        final int diff = state ^ old;
+        mDisabled = state;
+
+        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
+            if ((state & StatusBarManager.DISABLE_EXPAND) != 0) {
+                Slog.d(TAG, "DISABLE_EXPAND: yes");
+                animateCollapse();
+            }
+        }
+        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+            if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
+                if (mTicking) {
+                    mKicker.halt();
+                } else {
+                    mNotificationIconArea.setVisibility(View.INVISIBLE);
+                }
+            } else {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
+                if (!mExpandedVisible) {
+                    mNotificationIconArea.setVisibility(View.VISIBLE);
+                }
+            }
+        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+            if (mTicking && (state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_TICKER: yes");
+                mKicker.halt();
+            }
+        }
+        */
+    }
+
+    void performDisableActions(int net) {
+        /*
+        int old = mDisabled;
+        int diff = net ^ old;
+        mDisabled = net;
+
+        // act accordingly
+        if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) {
+            if ((net & StatusBarManager.DISABLE_EXPAND) != 0) {
+                Slog.d(TAG, "DISABLE_EXPAND: yes");
+                animateCollapse();
+            }
+        }
+        if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+            if ((net & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes");
+                if (mTicking) {
+                    mNotificationIconArea.setVisibility(View.INVISIBLE);
+                    mKicker.halt();
+                } else {
+                    mNotificationIconArea.setVisibility(View.INVISIBLE);
+                }
+            } else {
+                Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no");
+                if (!mExpandedVisible) {
+                    mNotificationIconArea.setVisibility(View.VISIBLE);
+                }
+            }
+        } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+            if (mTicking && (net & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) {
+                mKicker.halt();
+            }
+        }
+        */
+    }
+
+    private void tick(StatusBarNotification n) {
+        // Show the ticker if one is requested. Also don't do this
+        // until status bar window is attached to the window manager,
+        // because...  well, what's the point otherwise?  And trying to
+        // run a ticker without being attached will crash!
+        if (n.notification.tickerText != null && mStatusBarView.getWindowToken() != null) {
+            if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS
+                            | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) {
+                mKicker.addEntry(n);
+            }
+        }
+    }
+
+    private class KickerController {
+        View mView;
+        ImageView mKickerIcon;
+        TextSwitcher mKickerText;
+
+        public KickerController(Context context, View sb) {
+            mView = sb.findViewById(R.id.ticker);
+            mKickerIcon = (ImageView) mView.findViewById(R.id.tickerIcon);
+            mKickerText = (TextSwitcher) mView.findViewById(R.id.tickerText);
+        }
+
+        public void halt() {
+            tickerHalting();
+        }
+
+        public void addEntry(StatusBarNotification n) {
+            mKickerIcon.setImageResource(n.notification.icon);
+            mKickerText.setCurrentText(n.notification.tickerText);
+            tickerStarting();
+        }
+
+        public void tickerStarting() {
+            mTicking = true;
+            mIconLayout.setVisibility(View.GONE);
+            mKickerView.setVisibility(View.VISIBLE);
+        }
+
+        public void tickerDone() {
+            mIconLayout.setVisibility(View.VISIBLE);
+            mKickerView.setVisibility(View.GONE);
+            mTicking = false;
+        }
+
+        public void tickerHalting() {
+            mIconLayout.setVisibility(View.VISIBLE);
+            mKickerView.setVisibility(View.GONE);
+            mTicking = false;
+        }
     }
 
     public void animateExpand() {
@@ -405,8 +547,9 @@
     }
 
     StatusBarIconView addNotificationViews(IBinder key, StatusBarNotification notification) {
-        NotificationData list = mNotns;
-        ViewGroup parent = mPile;
+        if (DEBUG) {
+            Slog.d(TAG, "addNotificationViews(key=" + key + ", notification=" + notification);
+        }
         // Construct the icon.
         final StatusBarIconView iconView = new StatusBarIconView(this,
                 notification.pkg + "/0x" + Integer.toHexString(notification.id));
@@ -422,19 +565,13 @@
         }
         // Construct the expanded view.
         NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
-        if (!inflateViews(entry, parent)) {
+        if (!inflateViews(entry, mPile)) {
             handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
                     + notification);
             return null;
         }
-        // Add the expanded view.
-        final int viewIndex = list.add(entry);
-        if (parent != null) parent.addView(entry.row, viewIndex);
         // Add the icon.
-//        final int iconIndex = 0; // XXX: sort into ongoing and regular buckets
-//        mIconLayout.addView(iconView, iconIndex,
-//                new LinearLayout.LayoutParams(mIconSize, mIconSize));
-
+        mNotns.add(entry);
         refreshIcons();
 
         return iconView;
@@ -443,13 +580,25 @@
     private void refreshIcons() {
         // XXX: need to implement a new limited linear layout class
         // to avoid removing & readding everything
-        mIconLayout.removeAllViews();
+
         int N = mNotns.size();
         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize, mIconSize);
+
+        if (DEBUG) {
+            Slog.d(TAG, "refreshing icons (" + N + " notifications, mIconLayout="
+                    + mIconLayout + ", mPile=" + mPile);
+        }
+
+        mIconLayout.removeAllViews();
         for (int i=0; i<4; i++) {
             if (i>=N) break;
             mIconLayout.addView(mNotns.get(N-i-1).icon, i, params);
         }
+
+        mPile.removeAllViews();
+        for (int i=0; i<N; i++) {
+            mPile.addView(mNotns.get(N-i-1).row);
+        }
     }
 
     private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 73fa93c..8a6428b 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -406,7 +406,12 @@
 
     @Override
     public final void openPanel(int featureId, KeyEvent event) {
-        openPanel(getPanelState(featureId, true), event);
+        if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null &&
+                mActionBar.isOverflowReserved()) {
+            mActionBar.showOverflowMenu();
+        } else {
+            openPanel(getPanelState(featureId, true), event);
+        }
     }
 
     private void openPanel(PanelFeatureState st, KeyEvent event) {
@@ -497,7 +502,10 @@
 
     @Override
     public final void closePanel(int featureId) {
-        if (featureId == FEATURE_CONTEXT_MENU) {
+        if (featureId == FEATURE_OPTIONS_PANEL && mActionBar != null &&
+                mActionBar.isOverflowReserved()) {
+            mActionBar.hideOverflowMenu();
+        } else if (featureId == FEATURE_CONTEXT_MENU) {
             closeContextMenu();
         } else {
             closePanel(getPanelState(featureId, true), true);
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 252b42a..1c7faa4 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1696,7 +1696,10 @@
     // Delegate master volume control to effect in output mix effect chain if needed
     sp<EffectChain> chain = getEffectChain_l(AudioSystem::SESSION_OUTPUT_MIX);
     if (chain != 0) {
-        uint32_t v = (uint32_t)(masterVolume * (1 << 24));
+        uint32_t v = 0;
+        if (!masterMute) {
+            v = (uint32_t)(masterVolume * (1 << 24));
+        }
         chain->setVolume_l(&v, &v);
         masterVolume = (float)((v + (1 << 23)) >> 24);
         chain.clear();
@@ -1750,7 +1753,7 @@
 
             // compute volume for this track
             int16_t left, right, aux;
-            if (track->isMuted() || masterMute || track->isPausing() ||
+            if (track->isMuted() || track->isPausing() ||
                 mStreamTypes[track->type()].mute) {
                 left = right = aux = 0;
                 if (track->isPausing()) {
@@ -5351,7 +5354,7 @@
         return;
     }
 
-    if (mState == ACTIVE || mState == STOPPING || mState == STOPPED) {
+    if (mState == ACTIVE || mState == STOPPING || mState == STOPPED || mState == RESTART) {
         // do 32 bit to 16 bit conversion for auxiliary effect input buffer
         if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
             AudioMixer::ditherAndClamp(mConfig.inputCfg.buffer.s32,
@@ -6032,8 +6035,8 @@
 AudioFlinger::EffectChain::EffectChain(const wp<ThreadBase>& wThread,
                                         int sessionId)
     : mThread(wThread), mSessionId(sessionId), mActiveTrackCnt(0), mOwnInBuffer(false),
-            mVolumeCtrlIdx(-1), mLeftVolume(0), mRightVolume(0),
-            mNewLeftVolume(0), mNewRightVolume(0)
+            mVolumeCtrlIdx(-1), mLeftVolume(UINT_MAX), mRightVolume(UINT_MAX),
+            mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX)
 {
     mStrategy = AudioSystem::getStrategyForStream(AudioSystem::MUSIC);
 }
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index cc1566b..ae4e168 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -314,14 +314,14 @@
             case ConnectivityManager.TYPE_WIFI:
                 if (DBG) Slog.v(TAG, "Starting Wifi Service.");
                 WifiStateTracker wst = new WifiStateTracker(context, mHandler);
-                WifiService wifiService = new WifiService(context, wst);
+                WifiService wifiService = new WifiService(context);
                 ServiceManager.addService(Context.WIFI_SERVICE, wifiService);
-                wifiService.startWifi();
+                wifiService.checkAndStartWifi();
                 mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
                 wst.startMonitoring();
 
                 //TODO: as part of WWS refactor, create only when needed
-                mWifiWatchdogService = new WifiWatchdogService(context, wst);
+                mWifiWatchdogService = new WifiWatchdogService(context);
 
                 break;
             case ConnectivityManager.TYPE_MOBILE:
@@ -1205,16 +1205,6 @@
         sendConnectedBroadcast(info);
     }
 
-    private void handleScanResultsAvailable(NetworkInfo info) {
-        int networkType = info.getType();
-        if (networkType != ConnectivityManager.TYPE_WIFI) {
-            if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " +
-                    info.getTypeName() + " network. Don't know how to handle.");
-        }
-
-        mNetTrackers[networkType].interpretScanResultsAvailable();
-    }
-
     private void handleNotificationChange(boolean visible, int id,
             Notification notification) {
         NotificationManager notificationManager = (NotificationManager) mContext
@@ -1619,11 +1609,6 @@
                     }
                     break;
 
-                case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE:
-                    info = (NetworkInfo) msg.obj;
-                    handleScanResultsAvailable(info);
-                    break;
-
                 case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED:
                     handleNotificationChange(msg.arg1 == 1, msg.arg2,
                             (Notification) msg.obj);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 36b3a5e..3fd9907 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -77,9 +77,12 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
 
 /**
  * This class provides a system service that manages input methods.
@@ -1508,21 +1511,22 @@
             hideInputMethodMenuLocked();
 
             int N = immis.size();
-    
-            mItems = new CharSequence[N];
-            mIms = new InputMethodInfo[N];
-    
-            int j = 0;
+
+            final Map<CharSequence, InputMethodInfo> imMap =
+                new TreeMap<CharSequence, InputMethodInfo>(Collator.getInstance());
+
             for (int i = 0; i < N; ++i) {
                 InputMethodInfo property = immis.get(i);
                 if (property == null) {
                     continue;
                 }
-                mItems[j] = property.loadLabel(pm);
-                mIms[j] = property;
-                j++;
+                imMap.put(property.loadLabel(pm), property);
             }
-    
+
+            N = imMap.size();
+            mItems = imMap.keySet().toArray(new CharSequence[N]);
+            mIms = imMap.values().toArray(new InputMethodInfo[N]);
+
             int checkedItem = 0;
             for (int i = 0; i < N; ++i) {
                 if (mIms[i].getId().equals(lastInputMethodId)) {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index 15080b2..b43b33e 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -17,6 +17,8 @@
 package com.android.server;
 
 import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
@@ -26,25 +28,30 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateTracker;
+import android.net.wifi.WifiStateMachine;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.SupplicantState;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.ConnectivityManager;
 import android.net.InterfaceConfiguration;
-import android.net.NetworkStateTracker;
 import android.net.DhcpInfo;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.State;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Slog;
 
 import java.util.ArrayList;
@@ -72,7 +79,7 @@
     private static final String TAG = "WifiService";
     private static final boolean DBG = true;
 
-    private final WifiStateTracker mWifiStateTracker;
+    private final WifiStateMachine mWifiStateMachine;
 
     private Context mContext;
 
@@ -123,10 +130,63 @@
 
     private boolean mIsReceiverRegistered = false;
 
-    WifiService(Context context, WifiStateTracker tracker) {
+
+    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+    // Variables relating to the 'available networks' notification
+    /**
+     * The icon to show in the 'available networks' notification. This will also
+     * be the ID of the Notification given to the NotificationManager.
+     */
+    private static final int ICON_NETWORKS_AVAILABLE =
+            com.android.internal.R.drawable.stat_notify_wifi_in_range;
+    /**
+     * When a notification is shown, we wait this amount before possibly showing it again.
+     */
+    private final long NOTIFICATION_REPEAT_DELAY_MS;
+    /**
+     * Whether the user has set the setting to show the 'available networks' notification.
+     */
+    private boolean mNotificationEnabled;
+    /**
+     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
+     */
+    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+    /**
+     * The {@link System#currentTimeMillis()} must be at least this value for us
+     * to show the notification again.
+     */
+    private long mNotificationRepeatTime;
+    /**
+     * The Notification object given to the NotificationManager.
+     */
+    private Notification mNotification;
+    /**
+     * Whether the notification is being shown, as set by us. That is, if the
+     * user cancels the notification, we will not receive the callback so this
+     * will still be true. We only guarantee if this is false, then the
+     * notification is not showing.
+     */
+    private boolean mNotificationShown;
+    /**
+     * The number of continuous scans that must occur before consider the
+     * supplicant in a scanning state. This allows supplicant to associate with
+     * remembered networks that are in the scan results.
+     */
+    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+    /**
+     * The number of scans since the last network state change. When this
+     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+     * supplicant to actually be scanning. When the network state changes to
+     * something other than scanning, we reset this to 0.
+     */
+    private int mNumScansSinceNetworkStateChange;
+
+
+    WifiService(Context context) {
         mContext = context;
-        mWifiStateTracker = tracker;
-        mWifiStateTracker.enableRssiPolling(true);
+        mWifiStateMachine = new WifiStateMachine(mContext);
+        mWifiStateMachine.enableRssiPolling(true);
         mBatteryStats = BatteryStatsService.getService();
 
         mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
@@ -164,6 +224,42 @@
 
                 }
             },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED));
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                            // reset & clear notification on any wifi state change
+                            resetNotification();
+                        } else if (intent.getAction().equals(
+                                WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                            // reset & clear notification on a network connect & disconnect
+                            switch(mNetworkInfo.getDetailedState()) {
+                                case CONNECTED:
+                                case DISCONNECTED:
+                                    resetNotification();
+                                    break;
+                            }
+                        } else if (intent.getAction().equals(
+                                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                            checkAndSetNotification();
+                        }
+                    }
+                }, filter);
+
+        // Setting is in seconds
+        NOTIFICATION_REPEAT_DELAY_MS = Settings.Secure.getInt(context.getContentResolver(),
+                Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+        mNotificationEnabledSettingObserver.register();
     }
 
     /**
@@ -172,7 +268,7 @@
      *
      * This function is used only at boot time
      */
-    public void startWifi() {
+    public void checkAndStartWifi() {
         /* Start if Wi-Fi is enabled or the saved state indicates Wi-Fi was on */
         boolean wifiEnabled = !isAirplaneModeOn()
                 && (getPersistedWifiEnabled() || testAndClearWifiSavedState());
@@ -255,17 +351,13 @@
         Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, enabled ? 1 : 0);
     }
 
-    NetworkStateTracker getNetworkStateTracker() {
-        return mWifiStateTracker;
-    }
-
     /**
      * see {@link android.net.wifi.WifiManager#pingSupplicant()}
      * @return {@code true} if the operation succeeds, {@code false} otherwise
      */
     public boolean pingSupplicant() {
         enforceAccessPermission();
-        return mWifiStateTracker.pingSupplicant();
+        return mWifiStateMachine.pingSupplicant();
     }
 
     /**
@@ -274,7 +366,7 @@
      */
     public boolean startScan(boolean forceActive) {
         enforceChangePermission();
-        return mWifiStateTracker.startScan(forceActive);
+        return mWifiStateMachine.startScan(forceActive);
     }
 
     private void enforceAccessPermission() {
@@ -304,7 +396,7 @@
         enforceChangePermission();
 
         if (DBG) {
-            Slog.e(TAG, "Invoking mWifiStateTracker.setWifiEnabled\n");
+            Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
         }
 
         // set a flag if the user is enabling Wifi while in airplane mode
@@ -312,7 +404,7 @@
             mAirplaneModeOverwridden.set(true);
         }
 
-        mWifiStateTracker.setWifiEnabled(enable);
+        mWifiStateMachine.setWifiEnabled(enable);
         persistWifiEnabled(enable);
 
         if (enable) {
@@ -338,7 +430,7 @@
      */
     public int getWifiEnabledState() {
         enforceAccessPermission();
-        return mWifiStateTracker.getWifiState();
+        return mWifiStateMachine.getWifiState();
     }
 
     /**
@@ -362,7 +454,7 @@
             setWifiApConfiguration(wifiConfig);
         }
 
-        mWifiStateTracker.setWifiApEnabled(wifiConfig, enabled);
+        mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled);
 
         return true;
     }
@@ -377,7 +469,7 @@
      */
     public int getWifiApEnabledState() {
         enforceAccessPermission();
-        return mWifiStateTracker.getWifiApState();
+        return mWifiStateMachine.getWifiApState();
     }
 
     /**
@@ -427,7 +519,7 @@
      */
     public boolean disconnect() {
         enforceChangePermission();
-        return mWifiStateTracker.disconnectCommand();
+        return mWifiStateMachine.disconnectCommand();
     }
 
     /**
@@ -436,7 +528,7 @@
      */
     public boolean reconnect() {
         enforceChangePermission();
-        return mWifiStateTracker.reconnectCommand();
+        return mWifiStateMachine.reconnectCommand();
     }
 
     /**
@@ -445,7 +537,7 @@
      */
     public boolean reassociate() {
         enforceChangePermission();
-        return mWifiStateTracker.reassociateCommand();
+        return mWifiStateMachine.reassociateCommand();
     }
 
     /**
@@ -454,7 +546,7 @@
      */
     public List<WifiConfiguration> getConfiguredNetworks() {
         enforceAccessPermission();
-        return mWifiStateTracker.getConfiguredNetworks();
+        return mWifiStateMachine.getConfiguredNetworks();
     }
 
     /**
@@ -464,7 +556,7 @@
      */
     public int addOrUpdateNetwork(WifiConfiguration config) {
         enforceChangePermission();
-        return mWifiStateTracker.addOrUpdateNetwork(config);
+        return mWifiStateMachine.addOrUpdateNetwork(config);
     }
 
      /**
@@ -475,7 +567,7 @@
      */
     public boolean removeNetwork(int netId) {
         enforceChangePermission();
-        return mWifiStateTracker.removeNetwork(netId);
+        return mWifiStateMachine.removeNetwork(netId);
     }
 
     /**
@@ -487,7 +579,7 @@
      */
     public boolean enableNetwork(int netId, boolean disableOthers) {
         enforceChangePermission();
-        return mWifiStateTracker.enableNetwork(netId, disableOthers);
+        return mWifiStateMachine.enableNetwork(netId, disableOthers);
     }
 
     /**
@@ -498,7 +590,7 @@
      */
     public boolean disableNetwork(int netId) {
         enforceChangePermission();
-        return mWifiStateTracker.disableNetwork(netId);
+        return mWifiStateMachine.disableNetwork(netId);
     }
 
     /**
@@ -511,7 +603,7 @@
          * Make sure we have the latest information, by sending
          * a status request to the supplicant.
          */
-        return mWifiStateTracker.requestConnectionInfo();
+        return mWifiStateMachine.requestConnectionInfo();
     }
 
     /**
@@ -521,7 +613,7 @@
      */
     public List<ScanResult> getScanResults() {
         enforceAccessPermission();
-        return mWifiStateTracker.getScanResultsList();
+        return mWifiStateMachine.getScanResultsList();
     }
 
     /**
@@ -533,7 +625,7 @@
     public boolean saveConfiguration() {
         boolean result = true;
         enforceChangePermission();
-        return mWifiStateTracker.saveConfig();
+        return mWifiStateMachine.saveConfig();
     }
 
     /**
@@ -576,7 +668,7 @@
                     numChannels);
         }
 
-        mWifiStateTracker.setNumAllowedChannels(numChannels);
+        mWifiStateMachine.setNumAllowedChannels(numChannels);
 
         return true;
     }
@@ -596,7 +688,7 @@
          * Wi-Fi is not currently enabled), get the value from
          * Settings.
          */
-        numChannels = mWifiStateTracker.getNumAllowedChannels();
+        numChannels = mWifiStateMachine.getNumAllowedChannels();
         if (numChannels < 0) {
             numChannels = Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS,
@@ -622,9 +714,59 @@
      */
     public DhcpInfo getDhcpInfo() {
         enforceAccessPermission();
-        return mWifiStateTracker.getDhcpInfo();
+        return mWifiStateMachine.getDhcpInfo();
     }
 
+    /**
+     * see {@link android.net.wifi.WifiManager#startWifi}
+     *
+     */
+    public void startWifi() {
+        enforceChangePermission();
+        /* TODO: may be add permissions for access only to connectivity service
+         * TODO: if a start issued, keep wifi alive until a stop issued irrespective
+         * of WifiLock & device idle status unless wifi enabled status is toggled
+         */
+
+        mWifiStateMachine.setDriverStart(true);
+        mWifiStateMachine.reconnectCommand();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#stopWifi}
+     *
+     */
+    public void stopWifi() {
+        enforceChangePermission();
+        /* TODO: may be add permissions for access only to connectivity service
+         * TODO: if a stop is issued, wifi is brought up only by startWifi
+         * unless wifi enabled status is toggled
+         */
+        mWifiStateMachine.setDriverStart(false);
+    }
+
+
+    /**
+     * see {@link android.net.wifi.WifiManager#addToBlacklist}
+     *
+     */
+    public void addToBlacklist(String bssid) {
+        enforceChangePermission();
+
+        mWifiStateMachine.addToBlacklist(bssid);
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#clearBlacklist}
+     *
+     */
+    public void clearBlacklist() {
+        enforceChangePermission();
+
+        mWifiStateMachine.clearBlacklist();
+    }
+
+
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -641,11 +783,11 @@
                 mAlarmManager.cancel(mIdleIntent);
                 mDeviceIdle = false;
                 mScreenOff = false;
-                mWifiStateTracker.enableRssiPolling(true);
+                mWifiStateMachine.enableRssiPolling(true);
             } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                 Slog.d(TAG, "ACTION_SCREEN_OFF");
                 mScreenOff = true;
-                mWifiStateTracker.enableRssiPolling(false);
+                mWifiStateMachine.enableRssiPolling(false);
                 /*
                  * Set a timer to put Wi-Fi to sleep, but only if the screen is off
                  * AND the "stay on while plugged in" setting doesn't match the
@@ -653,7 +795,7 @@
                  * or plugged in to AC).
                  */
                 if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) {
-                    WifiInfo info = mWifiStateTracker.requestConnectionInfo();
+                    WifiInfo info = mWifiStateMachine.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,
@@ -704,7 +846,7 @@
                         isBluetoothPlaying = true;
                     }
                 }
-                mWifiStateTracker.setBluetoothScanMode(isBluetoothPlaying);
+                mWifiStateMachine.setBluetoothScanMode(isBluetoothPlaying);
 
             } else {
                 return;
@@ -771,21 +913,21 @@
 
         /* Disable tethering when airplane mode is enabled */
         if (airplaneMode) {
-            mWifiStateTracker.setWifiApEnabled(null, false);
+            mWifiStateMachine.setWifiApEnabled(null, false);
         }
 
         if (wifiShouldBeEnabled) {
             if (wifiShouldBeStarted) {
-                mWifiStateTracker.setWifiEnabled(true);
-                mWifiStateTracker.setScanOnlyMode(
+                mWifiStateMachine.setWifiEnabled(true);
+                mWifiStateMachine.setScanOnlyMode(
                         strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY);
-                mWifiStateTracker.startWifi(true);
+                mWifiStateMachine.setDriverStart(true);
             } else {
-                mWifiStateTracker.requestCmWakeLock();
-                mWifiStateTracker.disconnectAndStop();
+                mWifiStateMachine.requestCmWakeLock();
+                mWifiStateMachine.setDriverStart(false);
             }
         } else {
-            mWifiStateTracker.setWifiEnabled(false);
+            mWifiStateMachine.setWifiEnabled(false);
         }
     }
 
@@ -832,17 +974,17 @@
                     + ", uid=" + Binder.getCallingUid());
             return;
         }
-        pw.println("Wi-Fi is " + mWifiStateTracker.getWifiStateByName());
+        pw.println("Wi-Fi is " + mWifiStateMachine.getWifiStateByName());
         pw.println("Stay-awake conditions: " +
                 Settings.System.getInt(mContext.getContentResolver(),
                                        Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0));
         pw.println();
 
         pw.println("Internal state:");
-        pw.println(mWifiStateTracker);
+        pw.println(mWifiStateMachine);
         pw.println();
         pw.println("Latest scan results:");
-        List<ScanResult> scanResults = mWifiStateTracker.getScanResultsList();
+        List<ScanResult> scanResults = mWifiStateMachine.getScanResultsList();
         if (scanResults != null && scanResults.size() != 0) {
             pw.println("  BSSID              Frequency   RSSI  Flags             SSID");
             for (ScanResult r : scanResults) {
@@ -1069,7 +1211,7 @@
             if (mMulticasters.size() != 0) {
                 return;
             } else {
-                mWifiStateTracker.startPacketFiltering();
+                mWifiStateMachine.startPacketFiltering();
             }
         }
     }
@@ -1084,7 +1226,7 @@
             // our new size == 1 (first call), but this function won't
             // be called often and by making the stopPacket call each
             // time we're less fragile and self-healing.
-            mWifiStateTracker.stopPacketFiltering();
+            mWifiStateMachine.stopPacketFiltering();
         }
 
         int uid = Binder.getCallingUid();
@@ -1121,7 +1263,7 @@
             removed.unlinkDeathRecipient();
         }
         if (mMulticasters.size() == 0) {
-            mWifiStateTracker.startPacketFiltering();
+            mWifiStateMachine.startPacketFiltering();
         }
 
         Long ident = Binder.clearCallingIdentity();
@@ -1140,4 +1282,166 @@
             return (mMulticasters.size() > 0);
         }
     }
+
+    private void checkAndSetNotification() {
+        // If we shouldn't place a notification on available networks, then
+        // don't bother doing any of the following
+        if (!mNotificationEnabled) return;
+
+        State state = mNetworkInfo.getState();
+        if ((state == NetworkInfo.State.DISCONNECTED)
+                || (state == NetworkInfo.State.UNKNOWN)) {
+            // Look for an open network
+            List<ScanResult> scanResults = mWifiStateMachine.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);
+    }
+
+    /**
+     * Clears variables related to tracking whether a notification has been
+     * shown recently and clears the current notification.
+     */
+    private void resetNotification() {
+        mNotificationRepeatTime = 0;
+        mNumScansSinceNetworkStateChange = 0;
+        setNotificationVisible(false, 0, false, 0);
+    }
+
+    /**
+     * Display or don't display a notification that there are open Wi-Fi networks.
+     * @param visible {@code true} if notification should be visible, {@code false} otherwise
+     * @param numNetworks the number networks seen
+     * @param force {@code true} to force notification to be shown/not-shown,
+     * even if it is already shown/not-shown.
+     * @param delay time in milliseconds after which the notification should be made
+     * visible or invisible.
+     */
+    private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+            int delay) {
+
+        // Since we use auto cancel on the notification, when the
+        // mNetworksAvailableNotificationShown is true, the notification may
+        // have actually been canceled.  However, when it is false we know
+        // for sure that it is not being shown (it will not be shown any other
+        // place than here)
+
+        // If it should be hidden and it is already hidden, then noop
+        if (!visible && !mNotificationShown && !force) {
+            return;
+        }
+
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        Message message;
+        if (visible) {
+
+            // Not enough time has passed to show the notification again
+            if (System.currentTimeMillis() < mNotificationRepeatTime) {
+                return;
+            }
+
+            if (mNotification == null) {
+                // Cache the Notification 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;
+
+            notificationManager.notify(ICON_NETWORKS_AVAILABLE, mNotification);
+            /*
+             * TODO: Clean up connectivity service & remove this
+             */
+            /* message = mCsHandler.obtainMessage(EVENT_NOTIFICATION_CHANGED, 1,
+                    ICON_NETWORKS_AVAILABLE, mNotification); */
+
+
+        } else {
+
+            notificationManager.cancel(ICON_NETWORKS_AVAILABLE);
+            /*
+             * TODO: Clean up connectivity service & remove this
+             */
+            /*
+            // 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 class NotificationEnabledSettingObserver extends ContentObserver {
+
+        public NotificationEnabledSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void register() {
+            ContentResolver cr = mContext.getContentResolver();
+            cr.registerContentObserver(Settings.Secure.getUriFor(
+                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+            mNotificationEnabled = getValue();
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+
+            mNotificationEnabled = getValue();
+            resetNotification();
+        }
+
+        private boolean getValue() {
+            return Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+        }
+    }
+
+
 }
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index be14cd3..46d6bef 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -27,7 +27,6 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
-import android.net.wifi.WifiStateTracker;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -77,7 +76,6 @@
     
     private Context mContext;
     private ContentResolver mContentResolver;
-    private WifiStateTracker mWifiStateTracker;
     private WifiManager mWifiManager;
     
     /**
@@ -108,10 +106,9 @@
     /** Whether the current AP check should be canceled. */
     private boolean mShouldCancel;
     
-    WifiWatchdogService(Context context, WifiStateTracker wifiStateTracker) {
+    WifiWatchdogService(Context context) {
         mContext = context;
         mContentResolver = context.getContentResolver();
-        mWifiStateTracker = wifiStateTracker;
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         
         createThread();
@@ -752,7 +749,7 @@
         // Black list this "bad" AP, this will cause an attempt to connect to another
         blacklistAp(ap.bssid);
         // Initiate an association to an alternate AP
-        mWifiStateTracker.reassociateCommand();
+        mWifiManager.reassociate();
     }
 
     private void blacklistAp(String bssid) {
@@ -763,7 +760,7 @@
         // Before taking action, make sure we should not cancel our processing
         if (shouldCancel()) return;
         
-        mWifiStateTracker.addToBlacklist(bssid);
+        mWifiManager.addToBlacklist(bssid);
 
         if (D) {
             myLogD("Blacklisting " + bssid);
@@ -858,7 +855,7 @@
              * (and blacklisted them). Clear the blacklist so the AP with best
              * signal is chosen.
              */
-            mWifiStateTracker.clearBlacklist();
+            mWifiManager.clearBlacklist();
             
             if (V) {
                 myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
@@ -929,7 +926,7 @@
      * should revert anything done by the watchdog monitoring.
      */
     private void handleReset() {
-        mWifiStateTracker.clearBlacklist();
+        mWifiManager.clearBlacklist();
         setIdleState(true);
     }
     
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index b1e8968..e4ef09c 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -7862,6 +7862,7 @@
                             } catch (RemoteException e) {
                                 // Ignore if process has died.
                             }
+                            notifyFocusChanged();
                         }
 
                         if (lastFocus != null) {
@@ -7871,7 +7872,6 @@
                             } catch (RemoteException e) {
                                 // Ignore if process has died.
                             }
-                            notifyFocusChanged();
                         }
                     }
                 } break;
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
index 1381a2d..2934e6a 100644
--- a/telephony/java/com/android/internal/telephony/CallManager.java
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -24,6 +24,7 @@
 import android.telephony.PhoneStateListener;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -163,6 +164,14 @@
     }
 
     /**
+     * Returns all the registered phone objects.
+     * @return all the registered phone objects.
+     */
+    public List<Phone> getAllPhones() {
+        return Collections.unmodifiableList(mPhones);
+    }
+
+    /**
      * Get current coarse-grained voice call state.
      * If the Call Manager has an active call and call waiting occurs,
      * then the phone state is RINGING not OFFHOOK
@@ -206,7 +215,7 @@
      * @param phone
      */
     public void unregisterPhone(Phone phone) {
-        if (phone != null && !mPhones.contains(phone)) {
+        if (phone != null && mPhones.contains(phone)) {
             mPhones.remove(phone);
             mRingingCalls.remove(phone.getRingingCall());
             mBackgroundCalls.remove(phone.getBackgroundCall());
diff --git a/telephony/java/com/android/internal/telephony/PhoneFactory.java b/telephony/java/com/android/internal/telephony/PhoneFactory.java
index 803b736..2e391cb 100644
--- a/telephony/java/com/android/internal/telephony/PhoneFactory.java
+++ b/telephony/java/com/android/internal/telephony/PhoneFactory.java
@@ -24,6 +24,8 @@
 
 import com.android.internal.telephony.cdma.CDMAPhone;
 import com.android.internal.telephony.gsm.GSMPhone;
+import com.android.internal.telephony.sip.SipPhone;
+import com.android.internal.telephony.sip.SipPhoneFactory;
 
 /**
  * {@hide}
@@ -175,4 +177,13 @@
             return phone;
         }
     }
+
+    /**
+     * Makes a {@link SipPhone} object.
+     * @param sipUri the local SIP URI the phone runs on
+     * @return the {@code SipPhone} object or null if the SIP URI is not valid
+     */
+    public static SipPhone makeSipPhone(String sipUri) {
+        return SipPhoneFactory.makePhone(sipUri, sContext, sPhoneNotifier);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index e11bd42..b35814c 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -73,8 +73,6 @@
     private static final String LOG_TAG = "SipPhone";
     private static final boolean LOCAL_DEBUG = true;
 
-    //private List<SipConnection> connections = new ArrayList<SipConnection>();
-
     // A call that is ringing or (call) waiting
     private SipCall ringingCall = new SipCall();
     private SipCall foregroundCall = new SipCall();
@@ -111,6 +109,10 @@
         return mProfile.getProfileName();
     }
 
+    public String getSipUri() {
+        return mProfile.getUriString();
+    }
+
     public boolean canTake(Object incomingCall) {
         synchronized (SipPhone.class) {
             if (!(incomingCall instanceof SipAudioCall)) return false;
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
index c9e9762..a1bdd2f 100644
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneFactory.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.telephony.sip;
 
-import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneNotifier;
 
 import android.content.Context;
@@ -26,42 +25,25 @@
 import java.text.ParseException;
 
 /**
- * @hide
+ * {@hide}
  */
 public class SipPhoneFactory {
-    private static PhoneNotifier sPhoneNotifier = makeDefaultPhoneNotifier();
-    private static Context sContext;
-
-    public static void makeDefaultPhones(Context context) {
-        makeDefaultPhone(context);
-    }
-
-    public static void makeDefaultPhone(Context context) {
-        sContext = context;
-        SipPhoneProxy.getInstance().setPhone(
-                makePhone("sip:anonymous@localhost"));
-    }
-
-    public static Phone getDefaultPhone() {
-       return SipPhoneProxy.getInstance();
-    }
-
-    public static SipPhone makePhone(String sipProfileUri) {
+    /**
+     * Makes a {@link SipPhone} object.
+     * @param sipUri the local SIP URI the phone runs on
+     * @param context {@code Context} needed to create a Phone object
+     * @param phoneNotifier {@code PhoneNotifier} needed to create a Phone
+     *      object
+     * @return the {@code SipPhone} object or null if the SIP URI is not valid
+     */
+    public static SipPhone makePhone(String sipUri, Context context,
+            PhoneNotifier phoneNotifier) {
         try {
-            SipProfile profile = new SipProfile.Builder(sipProfileUri).build();
-            return new SipPhone(sContext, sPhoneNotifier, profile);
+            SipProfile profile = new SipProfile.Builder(sipUri).build();
+            return new SipPhone(context, phoneNotifier, profile);
         } catch (ParseException e) {
-            Log.v("SipPhoneProxy", "setPhone", e);
+            Log.w("SipPhoneProxy", "setPhone", e);
             return null;
         }
     }
-
-    private static PhoneNotifier makeDefaultPhoneNotifier() {
-        try {
-            return new com.android.internal.telephony.SipPhoneNotifier();
-        } catch (Error e) {
-            Log.e("SipPhoneProxy", "makeDefaultPhoneNotifier", e);
-            throw e;
-        }
-    }
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
index b62db51..740f544 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/CallbackProxy.java
@@ -510,6 +510,14 @@
         obtainMessage(SET_GEOLOCATION_PERMISSION, allow ? 1 : 0, 0).sendToTarget();
     }
 
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        // Configuration is in WebKit, so stay on WebCore thread, but go via the TestShellActivity
+        // as we need access to the Webview.
+        mLayoutTestController.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
     public void overridePreference(String key, boolean value) {
         Message message = obtainMessage(OVERRIDE_PREFERENCE);
         message.getData().putString("key", key);
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
index acc0ff2..9f580a3 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/FileFilter.java
@@ -73,7 +73,6 @@
 
     static final String[] ignoreTestList = {
         "editing/selection/move-left-right.html", // Causes DumpRenderTree to hang
-        "fast/dom/DeviceOrientation/basic-operation.html", // Will cause crash until mock DeviceOrientationClient is hooked up.
         "fast/js/excessive-comma-usage.html", // Tests huge initializer list, causes OOM.
         "fast/js/regexp-charclass-crash.html", // RegExp is too large, causing OOM
         "fast/regex/test1.html", // Causes DumpRenderTree to hang with V8
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java
index 15288b5..9be2f1c 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/LayoutTestController.java
@@ -71,4 +71,8 @@
 
     // For XSSAuditor tests
     public void setXSSAuditorEnabled(boolean flag);
+
+    // For DeviceOrientation tests
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma);
 }
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index 8c7254c..db076da 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -150,6 +150,9 @@
         if (intent != null) {
             executeIntent(intent);
         }
+
+        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
+        mWebView.useMockDeviceOrientation();
     }
 
     @Override
@@ -494,6 +497,12 @@
         mGeolocationPermission = allow;
     }
 
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        mWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+
     public void overridePreference(String key, boolean value) {
         // TODO: We should look up the correct WebView for the frame which
         // called the layoutTestController method. Currently, we just use the
diff --git a/tests/DumpRenderTree2/Android.mk b/tests/DumpRenderTree2/Android.mk
index 948ad72..eddbb4b 100644
--- a/tests/DumpRenderTree2/Android.mk
+++ b/tests/DumpRenderTree2/Android.mk
@@ -5,6 +5,8 @@
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
 LOCAL_STATIC_JAVA_LIBRARIES := diff_match_patch
 
 LOCAL_PACKAGE_NAME := DumpRenderTree2
diff --git a/tests/DumpRenderTree2/AndroidManifest.xml b/tests/DumpRenderTree2/AndroidManifest.xml
index 9f6097a..dd0c4e9f 100644
--- a/tests/DumpRenderTree2/AndroidManifest.xml
+++ b/tests/DumpRenderTree2/AndroidManifest.xml
@@ -16,6 +16,8 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.dumprendertree2">
     <application>
+        <uses-library android:name="android.test.runner" />
+
         <activity android:name=".ui.DirListActivity"
                   android:label="Dump Render Tree 2"
                   android:configChanges="orientation">
@@ -25,8 +27,15 @@
             </intent-filter>
         </activity>
 
+        <!-- android:launchMode="singleTask" is there so we only have a one instance
+             of this activity. However, it doesn't seem to work exactly like described in the
+             documentation, because the behaviour of the application suggest
+             there is only a single task for all 3 activities. We don't understand
+             how exactly it all works, but at the moment it works just fine.
+             It can lead to some weird behaviour in the future. -->
         <activity android:name=".TestsListActivity"
-                  android:label="Tests' list activity">
+                  android:label="Tests' list activity"
+                  android:launchMode="singleTask">
         </activity>
 
         <activity android:name=".LayoutTestsExecutor"
@@ -38,6 +47,10 @@
         </service>
     </application>
 
+    <instrumentation android:name="com.android.dumprendertree2.scriptsupport.ScriptTestRunner"
+                     android:targetPackage="com.android.dumprendertree2"
+                     android:label="Layout tests script runner" />
+
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.WRITE_SDCARD" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
diff --git a/tests/DumpRenderTree2/assets/run_layout_tests.py b/tests/DumpRenderTree2/assets/run_layout_tests.py
new file mode 100644
index 0000000..b13d8c9
--- /dev/null
+++ b/tests/DumpRenderTree2/assets/run_layout_tests.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+"""Run layout tests on the device.
+
+  It runs the specified tests on the device, downloads the summaries to the temporary directory
+  and opens html details in the default browser.
+
+  Usage:
+    run_layout_tests.py PATH
+"""
+
+import sys
+import os
+import subprocess
+import logging
+import webbrowser
+import tempfile
+
+#TODO: These should not be hardcoded
+RESULTS_ABSOLUTE_PATH = "/sdcard/android/LayoutTests-results/"
+DETAILS_HTML = "details.html"
+SUMMARY_TXT = "summary.txt"
+
+def main():
+  if len(sys.argv) > 1:
+    path = sys.argv[1]
+  else:
+    path = ""
+
+  logging.basicConfig(level=logging.INFO, format='%(message)s')
+
+  tmpdir = tempfile.gettempdir()
+
+  # Run the tests in path
+  cmd = "adb shell am instrument "
+  cmd += "-e class com.android.dumprendertree2.scriptsupport.Starter#startLayoutTests "
+  cmd += "-e path \"" + path + "\" "
+  cmd +="-w com.android.dumprendertree2/com.android.dumprendertree2.scriptsupport.ScriptTestRunner"
+
+  logging.info("Running the tests...")
+  subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+
+  logging.info("Downloading the summaries...")
+
+  # Download the txt summary to tmp folder
+  summary_txt_tmp_path = os.path.join(tmpdir, SUMMARY_TXT)
+  cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + SUMMARY_TXT + " " + summary_txt_tmp_path
+  subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+
+  # Download the html summary to tmp folder
+  details_html_tmp_path = os.path.join(tmpdir, DETAILS_HTML)
+  cmd = "adb pull " + RESULTS_ABSOLUTE_PATH + DETAILS_HTML + " " + details_html_tmp_path
+  subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+
+  # Print summary to console
+  logging.info("All done.\n")
+  cmd = "cat " + summary_txt_tmp_path
+  os.system(cmd)
+  logging.info("")
+
+  # Open the browser with summary
+  webbrowser.open(details_html_tmp_path)
+
+if __name__ == "__main__":
+  main();
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java
new file mode 100644
index 0000000..5b7cbc4
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSender.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.webkit.WebView;
+
+/**
+ * A class that acts as a JS interface for webview to mock various touch events,
+ * mouse actions and key presses.
+ *
+ * The methods here just call corresponding methods on EventSenderImpl
+ * that contains the logic of how to execute the methods.
+ */
+public class EventSender {
+    EventSenderImpl mEventSenderImpl = new EventSenderImpl();
+
+    public void reset(WebView webView) {
+        mEventSenderImpl.reset(webView);
+    }
+
+    public void enableDOMUIEventLogging(int domNode) {
+        mEventSenderImpl.enableDOMUIEventLogging(domNode);
+    }
+
+    public void fireKeyboardEventsToElement(int domNode) {
+        mEventSenderImpl.fireKeyboardEventsToElement(domNode);
+    }
+
+    public void keyDown(String character, String[] withModifiers) {
+        mEventSenderImpl.keyDown(character, withModifiers);
+    }
+
+    public void keyDown(String character) {
+        keyDown(character, null);
+    }
+
+    public void leapForward(int milliseconds) {
+        mEventSenderImpl.leapForward(milliseconds);
+    }
+
+    public void mouseClick() {
+        mEventSenderImpl.mouseClick();
+    }
+
+    public void mouseDown() {
+        mEventSenderImpl.mouseDown();
+    }
+
+    public void mouseMoveTo(int x, int y) {
+        mEventSenderImpl.mouseMoveTo(x, y);
+    }
+
+    public void mouseUp() {
+        mEventSenderImpl.mouseUp();
+    }
+
+    public void touchStart() {
+        mEventSenderImpl.touchStart();
+    }
+
+    public void addTouchPoint(int x, int y) {
+        mEventSenderImpl.addTouchPoint(x, y);
+    }
+
+    public void updateTouchPoint(int id, int x, int y) {
+        mEventSenderImpl.updateTouchPoint(id, x, y);
+    }
+
+    public void setTouchModifier(String modifier, boolean enabled) {
+        mEventSenderImpl.setTouchModifier(modifier, enabled);
+    }
+
+    public void touchMove() {
+        mEventSenderImpl.touchMove();
+    }
+
+    public void releaseTouchPoint(int id) {
+        mEventSenderImpl.releaseTouchPoint(id);
+    }
+
+    public void touchEnd() {
+        mEventSenderImpl.touchEnd();
+    }
+
+    public void touchCancel() {
+        mEventSenderImpl.touchCancel();
+    }
+
+    public void clearTouchPoints() {
+        mEventSenderImpl.clearTouchPoints();
+    }
+
+    public void cancelTouchPoint(int id) {
+        mEventSenderImpl.cancelTouchPoint(id);
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
new file mode 100644
index 0000000..93e6137
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/EventSenderImpl.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.webkit.WebView;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * An implementation of EventSender
+ */
+public class EventSenderImpl {
+    private static final String LOG_TAG = "EventSenderImpl";
+
+    private static final int MSG_ENABLE_DOM_UI_EVENT_LOGGING = 0;
+    private static final int MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT = 1;
+    private static final int MSG_LEAP_FORWARD = 2;
+
+    private static final int MSG_KEY_DOWN = 3;
+
+    private static final int MSG_MOUSE_DOWN = 4;
+    private static final int MSG_MOUSE_UP = 5;
+    private static final int MSG_MOUSE_CLICK = 6;
+    private static final int MSG_MOUSE_MOVE_TO = 7;
+
+    private static final int MSG_ADD_TOUCH_POINT = 8;
+    private static final int MSG_TOUCH_START = 9;
+    private static final int MSG_UPDATE_TOUCH_POINT = 10;
+    private static final int MSG_TOUCH_MOVE = 11;
+    private static final int MSG_CLEAR_TOUCH_POINTS = 12;
+    private static final int MSG_TOUCH_CANCEL = 13;
+    private static final int MSG_RELEASE_TOUCH_POINT = 14;
+    private static final int MSG_TOUCH_END = 15;
+    private static final int MSG_SET_TOUCH_MODIFIER = 16;
+    private static final int MSG_CANCEL_TOUCH_POINT = 17;
+
+    public static class TouchPoint {
+        WebView mWebView;
+        private int mX;
+        private int mY;
+        private long mDownTime;
+        private boolean mReleased = false;
+        private boolean mMoved = false;
+        private boolean mCancelled = false;
+
+        public TouchPoint(WebView webView, int x, int y) {
+            mWebView = webView;
+            mX = scaleX(x);
+            mY = scaleY(y);
+        }
+
+        public int getX() {
+            return mX;
+        }
+
+        public int getY() {
+            return mY;
+        }
+
+        public boolean hasMoved() {
+            return mMoved;
+        }
+
+        public void move(int newX, int newY) {
+            mX = scaleX(newX);
+            mY = scaleY(newY);
+            mMoved = true;
+        }
+
+        public void resetHasMoved() {
+            mMoved = false;
+        }
+
+        public long getDownTime() {
+            return mDownTime;
+        }
+
+        public void setDownTime(long downTime) {
+            mDownTime = downTime;
+        }
+
+        public boolean isReleased() {
+            return mReleased;
+        }
+
+        public void release() {
+            mReleased = true;
+        }
+
+        public boolean isCancelled() {
+            return mCancelled;
+        }
+
+        public void cancel() {
+            mCancelled = true;
+        }
+
+        private int scaleX(int x) {
+            return (int)(x * mWebView.getScale()) - mWebView.getScrollX();
+        }
+
+        private int scaleY(int y) {
+            return (int)(y * mWebView.getScale()) - mWebView.getScrollY();
+        }
+    }
+
+    private List<TouchPoint> mTouchPoints;
+    private int mTouchMetaState;
+    private int mMouseX;
+    private int mMouseY;
+
+    private WebView mWebView;
+
+    private Handler mEventSenderHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            TouchPoint touchPoint;
+            Bundle bundle;
+            KeyEvent event;
+
+            switch (msg.what) {
+                case MSG_ENABLE_DOM_UI_EVENT_LOGGING:
+                    /** TODO: implement */
+                    break;
+
+                case MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT:
+                    /** TODO: implement */
+                    break;
+
+                case MSG_LEAP_FORWARD:
+                    /** TODO: implement */
+                    break;
+
+                case MSG_KEY_DOWN:
+                    bundle = (Bundle)msg.obj;
+                    String character = bundle.getString("character");
+                    String[] withModifiers = bundle.getStringArray("withModifiers");
+
+                    if (withModifiers != null && withModifiers.length > 0) {
+                        for (int i = 0; i < withModifiers.length; i++) {
+                            executeKeyEvent(KeyEvent.ACTION_DOWN,
+                                    modifierToKeyCode(withModifiers[i]));
+                        }
+                    }
+                    executeKeyEvent(KeyEvent.ACTION_DOWN,
+                            charToKeyCode(character.toLowerCase().toCharArray()[0]));
+                    break;
+
+                /** MOUSE */
+
+                case MSG_MOUSE_DOWN:
+                    /** TODO: Implement */
+                    break;
+
+                case MSG_MOUSE_UP:
+                    /** TODO: Implement */
+                    break;
+
+                case MSG_MOUSE_CLICK:
+                    /** TODO: Implement */
+                    break;
+
+                case MSG_MOUSE_MOVE_TO:
+                    int x = msg.arg1;
+                    int y = msg.arg2;
+
+                    event = null;
+                    if (x > mMouseX) {
+                        event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT);
+                    } else if (x < mMouseX) {
+                        event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_LEFT);
+                    }
+                    if (event != null) {
+                        mWebView.onKeyDown(event.getKeyCode(), event);
+                        mWebView.onKeyUp(event.getKeyCode(), event);
+                    }
+
+                    event = null;
+                    if (y > mMouseY) {
+                        event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN);
+                    } else if (y < mMouseY) {
+                        event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP);
+                    }
+                    if (event != null) {
+                        mWebView.onKeyDown(event.getKeyCode(), event);
+                        mWebView.onKeyUp(event.getKeyCode(), event);
+                    }
+
+                    mMouseX = x;
+                    mMouseY = y;
+                    break;
+
+                /** TOUCH */
+
+                case MSG_ADD_TOUCH_POINT:
+                    getTouchPoints().add(new TouchPoint(mWebView,
+                            msg.arg1, msg.arg2));
+                    if (getTouchPoints().size() > 1) {
+                        Log.w(LOG_TAG + "::MSG_ADD_TOUCH_POINT", "Added more than one touch point");
+                    }
+                    break;
+
+                case MSG_TOUCH_START:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    touchPoint = getTouchPoints().get(0);
+
+                    touchPoint.setDownTime(SystemClock.uptimeMillis());
+                    executeTouchEvent(touchPoint, MotionEvent.ACTION_DOWN);
+                    break;
+
+                case MSG_UPDATE_TOUCH_POINT:
+                    bundle = (Bundle)msg.obj;
+
+                    int id = bundle.getInt("id");
+                    if (id >= getTouchPoints().size()) {
+                        Log.w(LOG_TAG + "::MSG_UPDATE_TOUCH_POINT", "TouchPoint out of bounds: "
+                                + id);
+                        break;
+                    }
+
+                    getTouchPoints().get(id).move(bundle.getInt("x"), bundle.getInt("y"));
+                    break;
+
+                case MSG_TOUCH_MOVE:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    touchPoint = getTouchPoints().get(0);
+
+                    if (!touchPoint.hasMoved()) {
+                        return;
+                    }
+                    executeTouchEvent(touchPoint, MotionEvent.ACTION_MOVE);
+                    touchPoint.resetHasMoved();
+                    break;
+
+                case MSG_CANCEL_TOUCH_POINT:
+                    if (msg.arg1 >= getTouchPoints().size()) {
+                        Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: "
+                                + msg.arg1);
+                        break;
+                    }
+
+                    getTouchPoints().get(msg.arg1).cancel();
+                    break;
+
+                case MSG_TOUCH_CANCEL:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    touchPoint = getTouchPoints().get(0);
+
+                    if (touchPoint.isCancelled()) {
+                        executeTouchEvent(touchPoint, MotionEvent.ACTION_CANCEL);
+                    }
+                    break;
+
+                case MSG_RELEASE_TOUCH_POINT:
+                    if (msg.arg1 >= getTouchPoints().size()) {
+                        Log.w(LOG_TAG + "::MSG_RELEASE_TOUCH_POINT", "TouchPoint out of bounds: "
+                                + msg.arg1);
+                        break;
+                    }
+
+                    getTouchPoints().get(msg.arg1).release();
+                    break;
+
+                case MSG_TOUCH_END:
+                    /**
+                     * FIXME: At the moment we don't support multi-touch. Hence, we only examine
+                     * the first touch point. In future this method will need rewriting.
+                     */
+                    if (getTouchPoints().isEmpty()) {
+                        return;
+                    }
+                    touchPoint = getTouchPoints().get(0);
+
+                    executeTouchEvent(touchPoint, MotionEvent.ACTION_UP);
+                    if (touchPoint.isReleased()) {
+                        getTouchPoints().remove(0);
+                        touchPoint = null;
+                    }
+                    break;
+
+                case MSG_SET_TOUCH_MODIFIER:
+                    bundle = (Bundle)msg.obj;
+                    String modifier = bundle.getString("modifier");
+                    boolean enabled = bundle.getBoolean("enabled");
+
+                    int mask = 0;
+                    if ("alt".equals(modifier.toLowerCase())) {
+                        mask = KeyEvent.META_ALT_ON;
+                    } else if ("shift".equals(modifier.toLowerCase())) {
+                        mask = KeyEvent.META_SHIFT_ON;
+                    } else if ("ctrl".equals(modifier.toLowerCase())) {
+                        mask = KeyEvent.META_SYM_ON;
+                    }
+
+                    if (enabled) {
+                        mTouchMetaState |= mask;
+                    } else {
+                        mTouchMetaState &= ~mask;
+                    }
+
+                    break;
+
+                case MSG_CLEAR_TOUCH_POINTS:
+                    getTouchPoints().clear();
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    };
+
+    public void reset(WebView webView) {
+        mWebView = webView;
+        mTouchPoints = null;
+        mTouchMetaState = 0;
+        mMouseX = 0;
+        mMouseY = 0;
+    }
+
+    public void enableDOMUIEventLogging(int domNode) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_ENABLE_DOM_UI_EVENT_LOGGING);
+        msg.arg1 = domNode;
+        msg.sendToTarget();
+    }
+
+    public void fireKeyboardEventsToElement(int domNode) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_FIRE_KEYBOARD_EVENTS_TO_ELEMENT);
+        msg.arg1 = domNode;
+        msg.sendToTarget();
+    }
+
+    public void leapForward(int milliseconds) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_LEAP_FORWARD);
+        msg.arg1 = milliseconds;
+        msg.sendToTarget();
+    }
+
+    public void keyDown(String character, String[] withModifiers) {
+        Bundle bundle = new Bundle();
+        bundle.putString("character", character);
+        bundle.putStringArray("withModifiers", withModifiers);
+        mEventSenderHandler.obtainMessage(MSG_KEY_DOWN, bundle).sendToTarget();
+    }
+
+    /** MOUSE */
+
+    public void mouseDown() {
+        mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_DOWN);
+    }
+
+    public void mouseUp() {
+        mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_UP);
+    }
+
+    public void mouseClick() {
+        mEventSenderHandler.sendEmptyMessage(MSG_MOUSE_CLICK);
+    }
+
+    public void mouseMoveTo(int x, int y) {
+        mEventSenderHandler.obtainMessage(MSG_MOUSE_MOVE_TO, x, y).sendToTarget();
+    }
+
+    /** TOUCH */
+
+    public void addTouchPoint(int x, int y) {
+        mEventSenderHandler.obtainMessage(MSG_ADD_TOUCH_POINT, x, y).sendToTarget();
+    }
+
+    public void touchStart() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_START);
+    }
+
+    public void updateTouchPoint(int id, int x, int y) {
+        Bundle bundle = new Bundle();
+        bundle.putInt("id", id);
+        bundle.putInt("x", x);
+        bundle.putInt("y", y);
+        mEventSenderHandler.obtainMessage(MSG_UPDATE_TOUCH_POINT, bundle).sendToTarget();
+    }
+
+    public void touchMove() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_MOVE);
+    }
+
+    public void cancelTouchPoint(int id) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_CANCEL_TOUCH_POINT);
+        msg.arg1 = id;
+        msg.sendToTarget();
+    }
+
+    public void touchCancel() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_CANCEL);
+    }
+
+    public void releaseTouchPoint(int id) {
+        Message msg = mEventSenderHandler.obtainMessage(MSG_RELEASE_TOUCH_POINT);
+        msg.arg1 = id;
+        msg.sendToTarget();
+    }
+
+    public void touchEnd() {
+        mEventSenderHandler.sendEmptyMessage(MSG_TOUCH_END);
+    }
+
+    public void setTouchModifier(String modifier, boolean enabled) {
+        Bundle bundle = new Bundle();
+        bundle.putString("modifier", modifier);
+        bundle.putBoolean("enabled", enabled);
+        mEventSenderHandler.obtainMessage(MSG_SET_TOUCH_MODIFIER, bundle).sendToTarget();
+    }
+
+    public void clearTouchPoints() {
+        mEventSenderHandler.sendEmptyMessage(MSG_CLEAR_TOUCH_POINTS);
+    }
+
+    private List<TouchPoint> getTouchPoints() {
+        if (mTouchPoints == null) {
+            mTouchPoints = new LinkedList<TouchPoint>();
+        }
+
+        return mTouchPoints;
+    }
+
+    private void executeTouchEvent(TouchPoint touchPoint, int action) {
+        MotionEvent event =
+                MotionEvent.obtain(touchPoint.getDownTime(), SystemClock.uptimeMillis(),
+                action, touchPoint.getX(), touchPoint.getY(), mTouchMetaState);
+        mWebView.onTouchEvent(event);
+    }
+
+    private void executeKeyEvent(int action, int keyCode) {
+        KeyEvent event = new KeyEvent(action, keyCode);
+        mWebView.onKeyDown(event.getKeyCode(), event);
+    }
+
+    /**
+     * Assumes lowercase chars, case needs to be handled by calling function.
+     */
+    private static int charToKeyCode(char c) {
+        // handle numbers
+        if (c >= '0' && c <= '9') {
+            int offset = c - '0';
+            return KeyEvent.KEYCODE_0 + offset;
+        }
+
+        // handle characters
+        if (c >= 'a' && c <= 'z') {
+            int offset = c - 'a';
+            return KeyEvent.KEYCODE_A + offset;
+        }
+
+        // handle all others
+        switch (c) {
+            case '*':
+                return KeyEvent.KEYCODE_STAR;
+
+            case '#':
+                return KeyEvent.KEYCODE_POUND;
+
+            case ',':
+                return KeyEvent.KEYCODE_COMMA;
+
+            case '.':
+                return KeyEvent.KEYCODE_PERIOD;
+
+            case '\t':
+                return KeyEvent.KEYCODE_TAB;
+
+            case ' ':
+                return KeyEvent.KEYCODE_SPACE;
+
+            case '\n':
+                return KeyEvent.KEYCODE_ENTER;
+
+            case '\b':
+            case 0x7F:
+                return KeyEvent.KEYCODE_DEL;
+
+            case '~':
+                return KeyEvent.KEYCODE_GRAVE;
+
+            case '-':
+                return KeyEvent.KEYCODE_MINUS;
+
+            case '=':
+                return KeyEvent.KEYCODE_EQUALS;
+
+            case '(':
+                return KeyEvent.KEYCODE_LEFT_BRACKET;
+
+            case ')':
+                return KeyEvent.KEYCODE_RIGHT_BRACKET;
+
+            case '\\':
+                return KeyEvent.KEYCODE_BACKSLASH;
+
+            case ';':
+                return KeyEvent.KEYCODE_SEMICOLON;
+
+            case '\'':
+                return KeyEvent.KEYCODE_APOSTROPHE;
+
+            case '/':
+                return KeyEvent.KEYCODE_SLASH;
+
+            default:
+                return c;
+        }
+    }
+
+    private static int modifierToKeyCode(String modifier) {
+        if (modifier.equals("ctrlKey")) {
+            return KeyEvent.KEYCODE_ALT_LEFT;
+        } else if (modifier.equals("shiftKey")) {
+            return KeyEvent.KEYCODE_SHIFT_LEFT;
+        } else if (modifier.equals("altKey")) {
+            return KeyEvent.KEYCODE_SYM;
+        }
+
+        return KeyEvent.KEYCODE_UNKNOWN;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
index 8ff5e63..6db9571 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestController.java
@@ -89,4 +89,12 @@
         Log.w(LOG_TAG + "::setMockGeolocationError", "code: " + code + " message: " + message);
         MockGeolocation.getInstance().setError(code, message);
     }
-}
\ No newline at end of file
+
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        // Configuration is in WebKit, so stay on WebCore thread, but go via LayoutTestsExecutor
+        // as we need access to the Webview.
+        mLayoutTestsExecutor.setMockDeviceOrientation(
+                canProvideAlpha, alpha, canProvideBeta, beta, canProvideGamma, gamma);
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
index dc82c34..8cc4921 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -110,6 +110,8 @@
     private boolean mSetGeolocationPermissionCalled;
     private boolean mGeolocationPermission;
 
+    private EventSender mEventSender = new EventSender();
+
     private WakeLock mScreenDimLock;
 
     /** COMMUNICATION WITH ManagerService */
@@ -248,7 +250,7 @@
         mCurrentTestIndex = intent.getIntExtra(EXTRA_TEST_INDEX, -1);
         mTotalTestCount = mCurrentTestIndex + mTestsList.size();
 
-        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
         mScreenDimLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK
                 | PowerManager.ON_AFTER_RELEASE, "WakeLock in LayoutTester");
         mScreenDimLock.acquire();
@@ -269,6 +271,8 @@
         mCurrentWebView = new WebView(this);
         setupWebView(mCurrentWebView);
 
+        mEventSender.reset(mCurrentWebView);
+
         setContentView(mCurrentWebView);
         if (previousWebView != null) {
             Log.d(LOG_TAG + "::reset", "previousWebView != null");
@@ -280,6 +284,7 @@
         webView.setWebViewClient(mWebViewClient);
         webView.setWebChromeClient(mWebChromeClient);
         webView.addJavascriptInterface(mLayoutTestController, "layoutTestController");
+        webView.addJavascriptInterface(mEventSender, "eventSender");
 
         /**
          * Setting a touch interval of -1 effectively disables the optimisation in WebView
@@ -303,6 +308,9 @@
         webViewSettings.setDomStorageEnabled(true);
         webViewSettings.setWorkersEnabled(false);
         webViewSettings.setXSSAuditorEnabled(false);
+
+        // This is asynchronous, but it gets processed by WebCore before it starts loading pages.
+        mCurrentWebView.useMockDeviceOrientation();
     }
 
     private void startTests() {
@@ -462,8 +470,7 @@
     Handler mLayoutTestControllerHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            assert mCurrentState.isRunningState()
-                    : "mCurrentState = " + mCurrentState.name();
+            assert mCurrentState.isRunningState() : "mCurrentState = " + mCurrentState.name();
 
             switch (msg.what) {
                 case MSG_WAIT_UNTIL_DONE:
@@ -565,4 +572,10 @@
         msg.arg1 = allow ? 1 : 0;
         msg.sendToTarget();
     }
-}
\ No newline at end of file
+
+    public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
+            boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) {
+        mCurrentWebView.setMockDeviceOrientation(canProvideAlpha, alpha, canProvideBeta, beta,
+                canProvideGamma, gamma);
+    }
+}
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
index 3bbfb89..7ec3dd7c 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ManagerService.java
@@ -82,6 +82,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_FIRST_TEST:
+                    mSummarizer.reset();
                     Bundle bundle = msg.getData();
                     ensureNextTestSetup(bundle.getString("firstTest"), bundle.getInt("index"));
                     break;
@@ -93,6 +94,11 @@
 
                 case MSG_ALL_TESTS_FINISHED:
                     mSummarizer.summarize();
+                    Intent intent = new Intent(ManagerService.this, TestsListActivity.class);
+                    intent.setAction(Intent.ACTION_SHUTDOWN);
+                    /** This flag is needed because we send the intent from the service */
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    startActivity(intent);
                     break;
             }
         }
@@ -166,7 +172,8 @@
 
         Intent intent = new Intent(this, TestsListActivity.class);
         intent.setAction(Intent.ACTION_REBOOT);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        /** This flag is needed because we send the intent from the service */
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra("crashedTestIndex", mCurrentlyRunningTestIndex);
         startActivity(intent);
     }
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
index 1b73f97..e27ecc9 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/Summarizer.java
@@ -166,7 +166,8 @@
             "</script>";
 
     /** TODO: Make it a setting */
-    private static final String HTML_SUMMARY_RELATIVE_PATH = "summary.html";
+    private static final String HTML_DETAILS_RELATIVE_PATH = "details.html";
+    private static final String TXT_SUMMARY_RELATIVE_PATH = "summary.txt";
 
     private int mCrashedTestsCount = 0;
     private List<AbstractResult> mFailedNotIgnoredTests = new ArrayList<AbstractResult>();
@@ -176,6 +177,8 @@
     private FileFilter mFileFilter;
     private String mResultsRootDirPath;
 
+    private String mTitleString;
+
     public Summarizer(FileFilter fileFilter, String resultsRootDirPath) {
         mFileFilter = fileFilter;
         mResultsRootDirPath = resultsRootDirPath;
@@ -198,6 +201,35 @@
     }
 
     public void summarize() {
+        createHtmlDetails();
+        createTxtSummary();
+    }
+
+    public void reset() {
+        mCrashedTestsCount = 0;
+        mFailedNotIgnoredTests.clear();
+        mIgnoredTests.clear();
+        mPassedNotIgnoredTests.clear();
+        mTitleString = null;
+    }
+
+    private void createTxtSummary() {
+        StringBuilder txt = new StringBuilder();
+
+        txt.append(getTitleString() + "\n");
+        if (mCrashedTestsCount > 0) {
+            txt.append("CRASHED (total among all tests): " + mCrashedTestsCount + "\n");
+            txt.append("-------------");
+        }
+        txt.append("FAILED:  " + mFailedNotIgnoredTests.size() + "\n");
+        txt.append("IGNORED: " + mIgnoredTests.size() + "\n");
+        txt.append("PASSED:  " + mPassedNotIgnoredTests.size() + "\n");
+
+        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, TXT_SUMMARY_RELATIVE_PATH),
+                txt.toString().getBytes(), false);
+    }
+
+    private void createHtmlDetails() {
         StringBuilder html = new StringBuilder();
 
         html.append("<html><head>");
@@ -215,17 +247,24 @@
 
         html.append("</body></html>");
 
-        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_SUMMARY_RELATIVE_PATH),
+        FsUtils.writeDataToStorage(new File(mResultsRootDirPath, HTML_DETAILS_RELATIVE_PATH),
                 html.toString().getBytes(), false);
     }
 
+    private String getTitleString() {
+        if (mTitleString == null) {
+            int total = mFailedNotIgnoredTests.size() +
+                    mPassedNotIgnoredTests.size() +
+                    mIgnoredTests.size();
+            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+            mTitleString = " - total of " + total + " tests - " + dateFormat.format(new Date());
+        }
+
+        return mTitleString;
+    }
+
     private void createTopSummaryTable(StringBuilder html) {
-        int total = mFailedNotIgnoredTests.size() +
-                mPassedNotIgnoredTests.size() +
-                mIgnoredTests.size();
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
-        html.append("<h1> - total of " + total + " tests - ");
-        html.append(dateFormat.format(new Date()) + "</h1>");
+        html.append("<h1>" + getTitleString() + "</h1>");
 
         html.append("<table class=\"summary\">");
         createSummaryTableRow(html, "CRASHED", mCrashedTestsCount);
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java
index c21463b..c95199f 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListActivity.java
@@ -24,6 +24,8 @@
 import android.os.Message;
 import android.view.Window;
 
+import com.android.dumprendertree2.scriptsupport.OnEverythingFinishedCallback;
+
 import java.util.ArrayList;
 
 /**
@@ -57,6 +59,9 @@
     private ArrayList<String> mTestsList;
     private int mTotalTestCount;
 
+    private OnEverythingFinishedCallback mOnEverythingFinishedCallback;
+    private boolean mEverythingFinished;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -85,6 +90,15 @@
         new TestsListPreloaderThread(path, doneMsg).start();
     }
 
+    @Override
+    protected void onNewIntent(Intent intent) {
+        if (intent.getAction().equals(Intent.ACTION_REBOOT)) {
+            onCrashIntent(intent);
+        } else if (intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
+            onEverythingFinishedIntent(intent);
+        }
+    }
+
     /**
      * This method handles an intent that comes from ManageService when crash is detected.
      * The intent contains an index in mTestsList of the test that crashed. TestsListActivity
@@ -94,18 +108,28 @@
      * LayoutTestExecutor runs then as usual, sending reports to ManagerService. If it
      * detects the crash it sends a new intent and the flow repeats.
      */
-    @Override
-    protected void onNewIntent(Intent intent) {
-        if (!intent.getAction().equals(Intent.ACTION_REBOOT)) {
-            return;
-        }
-
+    private void onCrashIntent(Intent intent) {
         int nextTestToRun = intent.getIntExtra("crashedTestIndex", -1) + 1;
         if (nextTestToRun > 0 && nextTestToRun <= mTotalTestCount) {
             restartExecutor(nextTestToRun);
         }
     }
 
+    public void registerOnEverythingFinishedCallback(OnEverythingFinishedCallback callback) {
+        mOnEverythingFinishedCallback = callback;
+        if (mEverythingFinished) {
+            mOnEverythingFinishedCallback.onFinished();
+        }
+    }
+
+    private void onEverythingFinishedIntent(Intent intent) {
+        /** TODO: Show some kind of summary to the user */
+        mEverythingFinished = true;
+        if (mOnEverythingFinishedCallback != null) {
+            mOnEverythingFinishedCallback.onFinished();
+        }
+    }
+
     @Override
     protected void onSaveInstanceState(Bundle outState) {
         outState.putStringArrayList("testsList", mTestsList);
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
index f76105d..2145af7 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/TestsListPreloaderThread.java
@@ -68,15 +68,15 @@
         File file = new File(TESTS_ROOT_DIR_PATH, mRelativePath);
         if (!file.exists()) {
             Log.e(LOG_TAG + "::run", "Path does not exist: " + mRelativePath);
-            return;
+        } else {
+            /** Populate the tests' list accordingly */
+            if (file.isDirectory()) {
+                preloadTests(mRelativePath);
+            } else {
+                mTestsList.add(mRelativePath);
+            }
         }
 
-        /** Populate the tests' list accordingly */
-        if (file.isDirectory()) {
-            preloadTests(mRelativePath);
-        } else {
-            mTestsList.add(mRelativePath);
-        }
         mDoneMsg.obj = mTestsList;
         mDoneMsg.sendToTarget();
     }
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java
new file mode 100644
index 0000000..e1d4364
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/OnEverythingFinishedCallback.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2.scriptsupport;
+
+/**
+ * Callback used to inform scriptsupport.Starter that everything is finished and
+ * we can exit
+ */
+public interface OnEverythingFinishedCallback {
+    public void onFinished();
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java
new file mode 100644
index 0000000..78f58d5
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/ScriptTestRunner.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2.scriptsupport;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+
+/**
+ * Extends InstrumentationTestRunner to allow the script to pass arguments to the application
+ */
+public class ScriptTestRunner extends InstrumentationTestRunner {
+    String mTestsRelativePath;
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        mTestsRelativePath = arguments.getString("path");
+        super.onCreate(arguments);
+    }
+
+    public String getTestsRelativePath() {
+        return mTestsRelativePath;
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java
new file mode 100644
index 0000000..ddfae69
--- /dev/null
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/scriptsupport/Starter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dumprendertree2.scriptsupport;
+
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import com.android.dumprendertree2.TestsListActivity;
+
+/**
+ * A class which provides methods that can be invoked by a script running on the host machine to
+ * run the tests.
+ *
+ * It starts a TestsListActivity and does not return until all the tests finish executing.
+ */
+public class Starter extends ActivityInstrumentationTestCase2<TestsListActivity> {
+    private static final String LOG_TAG = "Starter";
+    private boolean mEverythingFinished;
+
+    public Starter() {
+        super(TestsListActivity.class);
+    }
+
+    /**
+     * This method is called from adb to start executing the tests. It doesn't return
+     * until everything is finished so that the script can wait for the end if it needs
+     * to.
+     */
+    public void startLayoutTests() {
+        ScriptTestRunner runner = (ScriptTestRunner)getInstrumentation();
+        String relativePath = runner.getTestsRelativePath();
+
+        Intent intent = new Intent();
+        intent.setClassName("com.android.dumprendertree2", "TestsListActivity");
+        intent.setAction(Intent.ACTION_RUN);
+        intent.putExtra(TestsListActivity.EXTRA_TEST_PATH, relativePath);
+        setActivityIntent(intent);
+        getActivity().registerOnEverythingFinishedCallback(new OnEverythingFinishedCallback() {
+            /** This method is safe to call on any thread */
+            @Override
+            public void onFinished() {
+                synchronized (Starter.this) {
+                    mEverythingFinished = true;
+                    Starter.this.notifyAll();
+                }
+            }
+        });
+
+        synchronized (this) {
+            while (!mEverythingFinished) {
+                try {
+                    this.wait();
+                } catch (InterruptedException e) {
+                    Log.e(LOG_TAG + "::startLayoutTests", e.getMessage());
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
index 661a8ec..af0d7d1 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/ui/DirListActivity.java
@@ -378,6 +378,10 @@
     private ListItem[] getDirList(String dirPath) {
         File dir = new File(mRootDirPath, dirPath);
 
+        if (!dir.exists()) {
+            return new ListItem[0];
+        }
+
         List<ListItem> subDirs = new ArrayList<ListItem>();
         List<ListItem> subFiles = new ArrayList<ListItem>();
 
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 73994f7..181b4c8 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -171,5 +171,23 @@
             </intent-filter>
         </activity>
 
+        <activity
+                android:name="SimplePathsActivity"
+                android:label="_SimplePaths">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <activity
+                android:name="AdvancedBlendActivity"
+                android:label="_AdvancedBlend">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java
new file mode 100644
index 0000000..6c80a6d
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/AdvancedBlendActivity.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ComposeShader;
+import android.graphics.LinearGradient;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class AdvancedBlendActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new ShadersView(this));
+    }
+
+    static class ShadersView extends View {
+        private BitmapShader mScaledShader;
+        private int mTexWidth;
+        private int mTexHeight;
+        private Paint mPaint;
+        private float mDrawWidth;
+        private float mDrawHeight;
+        private LinearGradient mHorGradient;
+        private ComposeShader mComposeShader;
+        private ComposeShader mCompose2Shader;
+        private ComposeShader mCompose3Shader;
+        private ComposeShader mCompose4Shader;
+        private ComposeShader mCompose5Shader;
+        private ComposeShader mCompose6Shader;
+        private BitmapShader mScaled2Shader;
+
+        ShadersView(Context c) {
+            super(c);
+
+            Bitmap texture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mTexWidth = texture.getWidth();
+            mTexHeight = texture.getHeight();
+            mDrawWidth = mTexWidth * 2.2f;
+            mDrawHeight = mTexHeight * 1.2f;
+
+            mScaledShader = new BitmapShader(texture, Shader.TileMode.MIRROR,
+                    Shader.TileMode.MIRROR);
+            Matrix m2 = new Matrix();
+            m2.setScale(0.5f, 0.5f);
+            mScaledShader.setLocalMatrix(m2);
+            
+            mScaled2Shader = new BitmapShader(texture, Shader.TileMode.MIRROR,
+                    Shader.TileMode.MIRROR);
+            Matrix m3 = new Matrix();
+            m3.setScale(0.1f, 0.1f);
+            mScaled2Shader.setLocalMatrix(m3);
+
+            mHorGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth, 0.0f,
+                    Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP);
+            
+            mComposeShader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.DARKEN);
+            mCompose2Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.LIGHTEN);
+            mCompose3Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.MULTIPLY);
+            mCompose4Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.SCREEN);
+            mCompose5Shader = new ComposeShader(mScaledShader, mHorGradient,
+                    PorterDuff.Mode.ADD);
+            mCompose6Shader = new ComposeShader(mHorGradient, mScaledShader,
+                    PorterDuff.Mode.OVERLAY);
+
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            canvas.save();
+            canvas.translate(40.0f, 40.0f);
+
+            mPaint.setShader(mComposeShader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose2Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose3Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.restore();
+            
+            canvas.save();
+            canvas.translate(40.0f + mDrawWidth + 40.0f, 40.0f);
+            
+            mPaint.setShader(mCompose4Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose5Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mDrawHeight);
+            mPaint.setShader(mCompose6Shader);
+            canvas.drawRect(0.0f, 0.0f, mDrawWidth, mDrawHeight, mPaint);
+            
+            canvas.restore();
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java
new file mode 100644
index 0000000..071a118
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/SimplePathsActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class SimplePathsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FrameLayout layout = new FrameLayout(this);
+        EditText text = new EditText(this);
+        layout.addView(text, new FrameLayout.LayoutParams(600, 350, Gravity.CENTER));
+        text.setText("This is an example of an EditText widget \n" +
+                "using simple paths to create the selection.");
+        //text.setSelection(0, text.getText().length());
+
+        setContentView(layout);
+    }
+}
\ No newline at end of file
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
index 3b5cf43..59f665c 100644
--- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
@@ -35,6 +35,7 @@
     static class CustomTextView extends View {
         private final Paint mMediumPaint;
         private final Paint mLargePaint;
+        private final Paint mStrikePaint;
 
         CustomTextView(Context c) {
             super(c);
@@ -45,6 +46,11 @@
             mLargePaint = new Paint();
             mLargePaint.setAntiAlias(true);
             mLargePaint.setTextSize(36.0f);
+            mStrikePaint = new Paint();
+            mStrikePaint.setAntiAlias(true);
+            mStrikePaint.setTextSize(16.0f);
+            mStrikePaint.setUnderlineText(true);
+            
         }
 
         @Override
@@ -61,6 +67,15 @@
             canvas.drawText("Hello OpenGL renderer!", 100, 100, mMediumPaint);
             canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint);
             
+            
+            canvas.drawText("Hello OpenGL renderer!", 500, 40, mStrikePaint);
+            mStrikePaint.setStrikeThruText(true);
+            canvas.drawText("Hello OpenGL renderer!", 500, 70, mStrikePaint);
+            mStrikePaint.setUnderlineText(false);
+            canvas.drawText("Hello OpenGL renderer!", 500, 100, mStrikePaint);
+            mStrikePaint.setStrikeThruText(false);
+            mStrikePaint.setUnderlineText(true);
+            
             canvas.save();
             canvas.clipRect(150.0f, 220.0f, 450.0f, 320.0f);
             canvas.drawText("Hello OpenGL renderer!", 100, 300, mLargePaint);
diff --git a/voip/java/android/net/sip/BinderHelper.java b/voip/java/android/net/sip/BinderHelper.java
deleted file mode 100644
index bd3da32..0000000
--- a/voip/java/android/net/sip/BinderHelper.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Looper;
-import android.util.Log;
-
-// TODO: throw away this class after moving SIP classes to framework
-// This class helps to get IBinder instance of a service in a blocking call.
-// The method cannot be called in app's main thread as the ServiceConnection
-// callback will.
-class BinderHelper<T extends IInterface> {
-    private Context mContext;
-    private IBinder mBinder;
-    private Class<T> mClass;
-
-    BinderHelper(Context context, Class<T> klass) {
-        mContext = context;
-        mClass = klass;
-    }
-
-    void startService() {
-        mContext.startService(new Intent(mClass.getName()));
-    }
-
-    void stopService() {
-        mContext.stopService(new Intent(mClass.getName()));
-    }
-
-    IBinder getBinder() {
-        // cannot call this method in app's main thread
-        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
-            throw new RuntimeException(
-                    "This method cannot be called in app's main thread");
-        }
-
-        final ConditionVariable cv = new ConditionVariable();
-        cv.close();
-        ServiceConnection c = new ServiceConnection() {
-            public synchronized void onServiceConnected(
-                    ComponentName className, IBinder binder) {
-                Log.v("BinderHelper", "service connected!");
-                mBinder = binder;
-                cv.open();
-                mContext.unbindService(this);
-            }
-
-            public void onServiceDisconnected(ComponentName className) {
-                cv.open();
-                mContext.unbindService(this);
-            }
-        };
-        if (mContext.bindService(new Intent(mClass.getName()), c, 0)) {
-            cv.block(4500);
-        }
-        return mBinder;
-    }
-}
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index 57e0bd2..474bc4b 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -343,8 +343,11 @@
     public synchronized void endCall() throws SipException {
         try {
             stopRinging();
-            if (mSipSession != null) mSipSession.endCall();
             stopCall(true);
+            mInCall = false;
+
+            // perform the above local ops first and then network op
+            if (mSipSession != null) mSipSession.endCall();
         } catch (Throwable e) {
             throwSipException(e);
         }
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index f28b41c..287a13a 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -68,9 +68,6 @@
 
     private ISipService mSipService;
 
-    // Will be removed once the SIP service is integrated into framework
-    private BinderHelper<ISipService> mBinderHelper;
-
     /**
      * Creates a manager instance and initializes the background SIP service.
      * Will be removed once the SIP service is integrated into framework.
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 6e0bc9d..3426af7 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -85,5 +85,13 @@
     WifiConfiguration getWifiApConfiguration();
 
     void setWifiApConfiguration(in WifiConfiguration wifiConfig);
+
+    void startWifi();
+
+    void stopWifi();
+
+    void addToBlacklist(String bssid);
+
+    void clearBlacklist();
 }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6fac902..339763a 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -293,6 +293,21 @@
     public static final String EXTRA_NEW_RSSI = "newRssi";
 
     /**
+     * Broadcast intent action indicating that the IP configuration
+     * changed on wifi.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String CONFIG_CHANGED_ACTION = "android.net.wifi.CONFIG_CHANGED";
+    /**
+     * The lookup key for a {@link android.net.NetworkProperties} object associated with the
+     * Wi-Fi network. Retrieve with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
+     * @hide
+     */
+    public static final String EXTRA_NETWORK_PROPERTIES = "networkProperties";
+
+    /**
      * The network IDs of the configured networks could have changed.
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -838,6 +853,82 @@
         }
     }
 
+   /**
+     * Start the driver and connect to network.
+     *
+     * This function will over-ride WifiLock and device idle status. For example,
+     * even if the device is idle or there is only a scan-only lock held,
+     * a start wifi would mean that wifi connection is kept active until
+     * a stopWifi() is sent.
+     *
+     * This API is used by WifiStateTracker
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean startWifi() {
+        try {
+            mService.startWifi();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Disconnect from a network (if any) and stop the driver.
+     *
+     * This function will over-ride WifiLock and device idle status. Wi-Fi
+     * stays inactive until a startWifi() is issued.
+     *
+     * This API is used by WifiStateTracker
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean stopWifi() {
+        try {
+            mService.stopWifi();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Add a bssid to the supplicant blacklist
+     *
+     * This API is used by WifiWatchdogService
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean addToBlacklist(String bssid) {
+        try {
+            mService.addToBlacklist(bssid);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Clear the supplicant blacklist
+     *
+     * This API is used by WifiWatchdogService
+     *
+     * @return {@code true} if the operation succeeds else {@code false}
+     * @hide
+     */
+    public boolean clearBlacklist() {
+        try {
+            mService.clearBlacklist();
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     /**
      * Allows an application to keep the Wi-Fi radio awake.
      * Normally the Wi-Fi radio may turn off when the user has not used the device in a while.
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index f2f8343..af3132f 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -19,14 +19,13 @@
 import android.util.Log;
 import android.util.Config;
 import android.net.NetworkInfo;
-import android.net.NetworkStateTracker;
 
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 
 /**
  * Listens for events from the wpa_supplicant server, and passes them on
- * to the {@link WifiStateTracker} for handling. Runs in its own thread.
+ * to the {@link WifiStateMachine} for handling. Runs in its own thread.
  *
  * @hide
  */
@@ -117,7 +116,7 @@
     private static Pattern mConnectedEventPattern =
         Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) ");
 
-    private final WifiStateTracker mWifiStateTracker;
+    private final WifiStateMachine mWifiStateMachine;
 
     /**
      * This indicates the supplicant connection for the monitor is closed
@@ -139,18 +138,14 @@
      */
     private static final int MAX_RECV_ERRORS    = 10;
 
-    public WifiMonitor(WifiStateTracker tracker) {
-        mWifiStateTracker = tracker;
+    public WifiMonitor(WifiStateMachine wifiStateMachine) {
+        mWifiStateMachine = wifiStateMachine;
     }
 
     public void startMonitoring() {
         new MonitorThread().start();
     }
 
-    public NetworkStateTracker getNetworkStateTracker() {
-        return mWifiStateTracker;
-    }
-
     class MonitorThread extends Thread {
         public MonitorThread() {
             super("WifiMonitor");
@@ -161,9 +156,9 @@
             if (connectToSupplicant()) {
                 // Send a message indicating that it is now possible to send commands
                 // to the supplicant
-                mWifiStateTracker.notifySupplicantConnection();
+                mWifiStateMachine.notifySupplicantConnection();
             } else {
-                mWifiStateTracker.notifySupplicantLost();
+                mWifiStateMachine.notifySupplicantLost();
                 return;
             }
 
@@ -259,7 +254,7 @@
                     }
 
                     // notify and exit
-                    mWifiStateTracker.notifySupplicantLost();
+                    mWifiStateMachine.notifySupplicantLost();
                     break;
                 } else {
                     handleEvent(event, eventData);
@@ -285,7 +280,7 @@
         }
 
         private void handlePasswordKeyMayBeIncorrect() {
-            mWifiStateTracker.notifyPasswordKeyMayBeIncorrect();
+            mWifiStateMachine.notifyPasswordKeyMayBeIncorrect();
         }
 
         private void handleDriverEvent(String state) {
@@ -293,11 +288,11 @@
                 return;
             }
             if (state.equals("STOPPED")) {
-                mWifiStateTracker.notifyDriverStopped();
+                mWifiStateMachine.notifyDriverStopped();
             } else if (state.equals("STARTED")) {
-                mWifiStateTracker.notifyDriverStarted();
+                mWifiStateMachine.notifyDriverStarted();
             } else if (state.equals("HANGED")) {
-                mWifiStateTracker.notifyDriverHung();
+                mWifiStateMachine.notifyDriverHung();
             }
         }
 
@@ -318,7 +313,7 @@
                     break;
 
                 case SCAN_RESULTS:
-                    mWifiStateTracker.notifyScanResultsAvailable();
+                    mWifiStateMachine.notifyScanResultsAvailable();
                     break;
 
                 case UNKNOWN:
@@ -375,7 +370,7 @@
             if (newSupplicantState == SupplicantState.INVALID) {
                 Log.w(TAG, "Invalid supplicant state: " + newState);
             }
-            mWifiStateTracker.notifySupplicantStateChange(networkId, BSSID, newSupplicantState);
+            mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState);
         }
     }
 
@@ -395,7 +390,7 @@
                 }
             }
         }
-        mWifiStateTracker.notifyNetworkStateChange(newState, BSSID, networkId);
+        mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId);
     }
 
     /**
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
new file mode 100644
index 0000000..845508b
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -0,0 +1,3571 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
+
+/**
+ * TODO: Add soft AP states as part of WIFI_STATE_XXX
+ * Retain WIFI_STATE_ENABLING that indicates driver is loading
+ * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started
+ * and WIFI_STATE_FAILED for failure
+ * Deprecate WIFI_STATE_UNKNOWN
+ *
+ * Doing this will simplify the logic for sending broadcasts
+ */
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
+import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
+
+import android.app.ActivityManagerNative;
+import android.net.NetworkInfo;
+import android.net.DhcpInfo;
+import android.net.NetworkUtils;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkProperties;
+import android.os.Binder;
+import android.os.Message;
+import android.os.Parcelable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.Process;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.app.backup.IBackupManager;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothA2dp;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.Context;
+import android.database.ContentObserver;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
+
+/**
+ * Track the state of Wifi connectivity. All event handling is done here,
+ * and all changes in connectivity state are initiated here.
+ *
+ * @hide
+ */
+//TODO: we still need frequent scanning for the case when
+// we issue disconnect but need scan results for open network notification
+public class WifiStateMachine extends HierarchicalStateMachine {
+
+    private static final String TAG = "WifiStateMachine";
+    private static final String NETWORKTYPE = "WIFI";
+    private static final boolean DBG = false;
+
+    /* TODO: fetch a configurable interface */
+    private static final String SOFTAP_IFACE = "wl0.1";
+
+    private WifiMonitor mWifiMonitor;
+    private INetworkManagementService nwService;
+    private ConnectivityManager mCm;
+
+    /* Scan results handling */
+    private List<ScanResult> mScanResults;
+    private static final Pattern scanResultPattern = Pattern.compile("\t+");
+    private static final int SCAN_RESULT_CACHE_SIZE = 80;
+    private final LinkedHashMap<String, ScanResult> mScanResultCache;
+
+    private String mInterfaceName;
+
+    private int mNumAllowedChannels = 0;
+    private int mLastSignalLevel = -1;
+    private String mLastBssid;
+    private int mLastNetworkId;
+    private boolean mEnableRssiPolling = false;
+    private boolean mPasswordKeyMayBeIncorrect = false;
+    private boolean mUseStaticIp = false;
+    private int mReconnectCount = 0;
+    private boolean mIsScanMode = false;
+    private boolean mConfigChanged = false;
+
+    /**
+     * Instance of the bluetooth headset helper. This needs to be created
+     * 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;
+
+    // Held during driver load and unload
+    private static PowerManager.WakeLock sWakeLock;
+
+    private Context mContext;
+
+    private DhcpInfo mDhcpInfo;
+    private WifiInfo mWifiInfo;
+    private NetworkInfo mNetworkInfo;
+    private SupplicantStateTracker mSupplicantStateTracker;
+
+    // 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 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;
+
+    /**
+     * 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},
+     *         {@link WifiManager#WIFI_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_STATE_UNKNOWN}
+     *
+     */
+    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
+
+    /**
+     * One of  {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     *
+     */
+    private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
+
+    private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
+    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
+
+    private final IBatteryStats mBatteryStats;
+
+    public WifiStateMachine(Context context) {
+        super(TAG);
+
+        mContext = context;
+
+        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        nwService = INetworkManagementService.Stub.asInterface(b);
+
+        mWifiMonitor = new WifiMonitor(this);
+        mDhcpInfo = new DhcpInfo();
+        mWifiInfo = new WifiInfo();
+        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
+        mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler());
+
+        mBluetoothHeadset = new BluetoothHeadset(mContext, null);
+        mNetworkProperties = new NetworkProperties();
+
+        mNetworkInfo.setIsAvailable(false);
+        mNetworkProperties.clear();
+        mLastBssid = null;
+        mLastNetworkId = -1;
+        mLastSignalLevel = -1;
+
+        mScanResultCache = new LinkedHashMap<String, ScanResult>(
+            SCAN_RESULT_CACHE_SIZE, 0.75f, true) {
+                /*
+                 * Limit the cache size by SCAN_RESULT_CACHE_SIZE
+                 * elements
+                 */
+                @Override
+                public boolean removeEldestEntry(Map.Entry eldest) {
+                    return SCAN_RESULT_CACHE_SIZE < this.size();
+                }
+        };
+
+        mSettingsObserver = new SettingsObserver(new Handler());
+
+        PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        sWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        addState(mDefaultState);
+            addState(mInitialState, mDefaultState);
+            addState(mDriverUnloadingState, mDefaultState);
+            addState(mDriverUnloadedState, mDefaultState);
+                addState(mDriverFailedState, mDriverUnloadedState);
+            addState(mDriverLoadingState, mDefaultState);
+            addState(mDriverLoadedState, mDefaultState);
+                addState(mWaitForSupState, mDriverLoadedState);
+            addState(mDriverSupReadyState, mDefaultState);
+                addState(mDriverStartingState, mDriverSupReadyState);
+                addState(mDriverStartedState, mDriverSupReadyState);
+                    addState(mScanModeState, mDriverStartedState);
+                    addState(mConnectModeState, mDriverStartedState);
+                        addState(mConnectingState, mConnectModeState);
+                        addState(mConnectedState, mConnectModeState);
+                        addState(mDisconnectingState, mConnectModeState);
+                        addState(mDisconnectedState, mConnectModeState);
+                addState(mDriverStoppingState, mDriverSupReadyState);
+                addState(mDriverStoppedState, mDriverSupReadyState);
+            addState(mSoftApStartedState, mDefaultState);
+
+        setInitialState(mInitialState);
+
+        if (DBG) setDbg(true);
+
+        //start the state machine
+        start();
+    }
+
+    /*********************************************************
+     * Methods exposed for public use
+     ********************************************************/
+
+    /**
+     * TODO: doc
+     */
+    public boolean pingSupplicant() {
+        return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public boolean startScan(boolean forceActive) {
+        return sendSyncMessage(obtainMessage(CMD_START_SCAN, forceActive ?
+                SCAN_ACTIVE : SCAN_PASSIVE, 0)).boolValue;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setWifiEnabled(boolean enable) {
+        mLastEnableUid.set(Binder.getCallingUid());
+        if (enable) {
+            /* Argument is the state that is entered prior to load */
+            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
+            sendMessage(CMD_START_SUPPLICANT);
+        } else {
+            sendMessage(CMD_STOP_SUPPLICANT);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
+        mLastApEnableUid.set(Binder.getCallingUid());
+        if (enable) {
+            /* Argument is the state that is entered prior to load */
+            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0));
+            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
+        } else {
+            sendMessage(CMD_STOP_AP);
+            /* Argument is the state that is entered upon success */
+            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0));
+        }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public int 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
+     *
+     */
+    public WifiInfo requestConnectionInfo() {
+        return mWifiInfo;
+    }
+
+    public DhcpInfo getDhcpInfo() {
+        return mDhcpInfo;
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setDriverStart(boolean enable) {
+      if (enable) {
+          sendMessage(CMD_START_DRIVER);
+      } else {
+          sendMessage(CMD_STOP_DRIVER);
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setScanOnlyMode(boolean enable) {
+      if (enable) {
+          sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0));
+      } else {
+          sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0));
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public void setScanType(boolean active) {
+      if (active) {
+          sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_ACTIVE, 0));
+      } else {
+          sendMessage(obtainMessage(CMD_SET_SCAN_TYPE, SCAN_PASSIVE, 0));
+      }
+    }
+
+    /**
+     * TODO: doc
+     */
+    public List<ScanResult> 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 {
+            setNumAllowedChannels(
+                    Settings.Secure.getInt(mContext.getContentResolver(),
+                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
+        } catch (Settings.SettingNotFoundException e) {
+            if (mNumAllowedChannels != 0) {
+                setNumAllowedChannels(mNumAllowedChannels);
+            }
+            // otherwise, use the driver default
+        }
+    }
+
+    /**
+     * Set the number of radio frequency channels that are allowed to be used
+     * in the current regulatory domain.
+     * @param numChannels the number of allowed channels. Must be greater than 0
+     * and less than or equal to 16.
+     */
+    public void setNumAllowedChannels(int numChannels) {
+        sendMessage(obtainMessage(CMD_SET_NUM_ALLOWED_CHANNELS, numChannels, 0));
+    }
+
+    /**
+     * Get number of allowed channels
+     *
+     * @return channel count, -1 on failure
+     *
+     * TODO: this is not a public API and needs to be removed in favor
+     * of asynchronous reporting. unused for now.
+     */
+    public int getNumAllowedChannels() {
+        return -1;
+    }
+
+    /**
+     * Set bluetooth coex mode:
+     *
+     * @param mode
+     *  BLUETOOTH_COEXISTENCE_MODE_ENABLED
+     *  BLUETOOTH_COEXISTENCE_MODE_DISABLED
+     *  BLUETOOTH_COEXISTENCE_MODE_SENSE
+     */
+    public void setBluetoothCoexistenceMode(int mode) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0));
+    }
+
+    /**
+     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
+     * some of the low-level scan parameters used by the driver are changed to
+     * reduce interference with A2DP streaming.
+     *
+     * @param isBluetoothPlaying whether to enable or disable this mode
+     */
+    public void setBluetoothScanMode(boolean isBluetoothPlaying) {
+        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_SCAN_MODE, isBluetoothPlaying ? 1 : 0, 0));
+    }
+
+    /**
+     * Save configuration on supplicant
+     *
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     *
+     * TODO: deprecate this
+     */
+    public boolean 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();
+        }
+    }
+
+    /**
+     * message.arg2 is reserved to indicate synchronized
+     * message.obj is used to store SyncParams
+     */
+    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;
+            }
+        }
+        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");
+        }
+
+        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());
+
+        final Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(WifiManager.EXTRA_WIFI_AP_STATE, wifiApState);
+        intent.putExtra(WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE, previousWifiApState);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    /**
+     * Parse the scan result line passed to us by wpa_supplicant (helper).
+     * @param line the line to parse
+     * @return the {@link ScanResult} object
+     */
+    private ScanResult parseScanResult(String line) {
+        ScanResult scanResult = null;
+        if (line != null) {
+            /*
+             * Cache implementation (LinkedHashMap) is not synchronized, thus,
+             * must synchronized here!
+             */
+            synchronized (mScanResultCache) {
+                String[] result = scanResultPattern.split(line);
+                if (3 <= result.length && result.length <= 5) {
+                    String bssid = result[0];
+                    // bssid | frequency | level | flags | ssid
+                    int frequency;
+                    int level;
+                    try {
+                        frequency = Integer.parseInt(result[1]);
+                        level = Integer.parseInt(result[2]);
+                        /* some implementations avoid negative values by adding 256
+                         * so we need to adjust for that here.
+                         */
+                        if (level > 0) level -= 256;
+                    } catch (NumberFormatException e) {
+                        frequency = 0;
+                        level = 0;
+                    }
+
+                    /*
+                     * The formatting of the results returned by
+                     * wpa_supplicant is intended to make the fields
+                     * line up nicely when printed,
+                     * not to make them easy to parse. So we have to
+                     * apply some heuristics to figure out which field
+                     * is the SSID and which field is the flags.
+                     */
+                    String ssid;
+                    String flags;
+                    if (result.length == 4) {
+                        if (result[3].charAt(0) == '[') {
+                            flags = result[3];
+                            ssid = "";
+                        } else {
+                            flags = "";
+                            ssid = result[3];
+                        }
+                    } else if (result.length == 5) {
+                        flags = result[3];
+                        ssid = result[4];
+                    } else {
+                        // Here, we must have 3 fields: no flags and ssid
+                        // set
+                        flags = "";
+                        ssid = "";
+                    }
+
+                    // bssid + ssid is the hash key
+                    String key = bssid + ssid;
+                    scanResult = mScanResultCache.get(key);
+                    if (scanResult != null) {
+                        scanResult.level = level;
+                        scanResult.SSID = ssid;
+                        scanResult.capabilities = flags;
+                        scanResult.frequency = frequency;
+                    } else {
+                        // Do not add scan results that have no SSID set
+                        if (0 < ssid.trim().length()) {
+                            scanResult =
+                                new ScanResult(
+                                    ssid, bssid, flags, level, frequency);
+                            mScanResultCache.put(key, scanResult);
+                        }
+                    }
+                } else {
+                    Log.w(TAG, "Misformatted scan result text with " +
+                          result.length + " fields: " + line);
+                }
+            }
+        }
+
+        return scanResult;
+    }
+
+    /**
+     * scanResults input format
+     * 00:bb:cc:dd:cc:ee       2427    166     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net1
+     * 00:bb:cc:dd:cc:ff       2412    165     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net2
+     */
+    private void setScanResults(String scanResults) {
+        if (scanResults == null) {
+            return;
+        }
+
+        List<ScanResult> scanList = new ArrayList<ScanResult>();
+
+        int lineCount = 0;
+
+        int scanResultsLen = scanResults.length();
+        // Parse the result string, keeping in mind that the last line does
+        // not end with a newline.
+        for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) {
+            if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') {
+                ++lineCount;
+
+                if (lineCount == 1) {
+                    lineBeg = lineEnd + 1;
+                    continue;
+                }
+                if (lineEnd > lineBeg) {
+                    String line = scanResults.substring(lineBeg, lineEnd);
+                    ScanResult scanResult = parseScanResult(line);
+                    if (scanResult != null) {
+                        scanList.add(scanResult);
+                    } else {
+                        Log.w(TAG, "misformatted scan result for: " + line);
+                    }
+                }
+                lineBeg = lineEnd + 1;
+            }
+        }
+
+        mScanResults = scanList;
+    }
+
+    private void configureNetworkProperties() {
+        try {
+            mNetworkProperties.setInterface(NetworkInterface.getByName(mInterfaceName));
+        } catch (SocketException e) {
+            Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName +
+                    ". e=" + e);
+            return;
+        } catch (NullPointerException e) {
+            Log.e(TAG, "NPE creating NetworkInterface. e=" + e);
+            return;
+        }
+        // TODO - fix this for v6
+        try {
+            mNetworkProperties.addAddress(InetAddress.getByAddress(
+                    NetworkUtils.v4IntToArray(mDhcpInfo.ipAddress)));
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting IpAddress using " + mDhcpInfo + ", e=" + e);
+        }
+
+        try {
+            mNetworkProperties.setGateway(InetAddress.getByAddress(NetworkUtils.v4IntToArray(
+                    mDhcpInfo.gateway)));
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting Gateway using " + mDhcpInfo + ", e=" + e);
+        }
+
+        try {
+            mNetworkProperties.addDns(InetAddress.getByAddress(
+                    NetworkUtils.v4IntToArray(mDhcpInfo.dns1)));
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting Dns1 using " + mDhcpInfo + ", e=" + e);
+        }
+        try {
+            mNetworkProperties.addDns(InetAddress.getByAddress(
+                    NetworkUtils.v4IntToArray(mDhcpInfo.dns2)));
+
+        } catch (UnknownHostException e) {
+            Log.e(TAG, "Exception setting Dns2 using " + mDhcpInfo + ", e=" + e);
+        }
+        // TODO - add proxy info
+    }
+
+
+    private void checkUseStaticIp() {
+        mUseStaticIp = false;
+        final ContentResolver cr = mContext.getContentResolver();
+        try {
+            if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) {
+                return;
+            }
+        } catch (Settings.SettingNotFoundException e) {
+            return;
+        }
+
+        try {
+            String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP);
+            if (addr != null) {
+                mDhcpInfo.ipAddress = stringToIpAddr(addr);
+            } else {
+                return;
+            }
+            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY);
+            if (addr != null) {
+                mDhcpInfo.gateway = stringToIpAddr(addr);
+            } else {
+                return;
+            }
+            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK);
+            if (addr != null) {
+                mDhcpInfo.netmask = stringToIpAddr(addr);
+            } else {
+                return;
+            }
+            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1);
+            if (addr != null) {
+                mDhcpInfo.dns1 = stringToIpAddr(addr);
+            } else {
+                return;
+            }
+            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2);
+            if (addr != null) {
+                mDhcpInfo.dns2 = stringToIpAddr(addr);
+            } else {
+                mDhcpInfo.dns2 = 0;
+            }
+        } catch (UnknownHostException e) {
+            return;
+        }
+        mUseStaticIp = true;
+    }
+
+    private static int stringToIpAddr(String addrString) throws UnknownHostException {
+        try {
+            String[] parts = addrString.split("\\.");
+            if (parts.length != 4) {
+                throw new UnknownHostException(addrString);
+            }
+
+            int a = Integer.parseInt(parts[0])      ;
+            int b = Integer.parseInt(parts[1]) <<  8;
+            int c = Integer.parseInt(parts[2]) << 16;
+            int d = Integer.parseInt(parts[3]) << 24;
+
+            return a | b | c | d;
+        } catch (NumberFormatException ex) {
+            throw new UnknownHostException(addrString);
+        }
+    }
+
+    private int getMaxDhcpRetries() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                                      Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
+                                      DEFAULT_MAX_DHCP_RETRIES);
+    }
+
+    private class SettingsObserver extends ContentObserver {
+        public SettingsObserver(Handler handler) {
+            super(handler);
+            ContentResolver cr = mContext.getContentResolver();
+            cr.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.WIFI_USE_STATIC_IP), false, this);
+            cr.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.WIFI_STATIC_IP), false, this);
+            cr.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.WIFI_STATIC_GATEWAY), false, this);
+            cr.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.WIFI_STATIC_NETMASK), false, this);
+            cr.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.WIFI_STATIC_DNS1), false, this);
+            cr.registerContentObserver(Settings.System.getUriFor(
+                Settings.System.WIFI_STATIC_DNS2), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+
+            boolean wasStaticIp = mUseStaticIp;
+            int oIp, oGw, oMsk, oDns1, oDns2;
+            oIp = oGw = oMsk = oDns1 = oDns2 = 0;
+            if (wasStaticIp) {
+                oIp = mDhcpInfo.ipAddress;
+                oGw = mDhcpInfo.gateway;
+                oMsk = mDhcpInfo.netmask;
+                oDns1 = mDhcpInfo.dns1;
+                oDns2 = mDhcpInfo.dns2;
+            }
+            checkUseStaticIp();
+
+            if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
+                return;
+            }
+
+            boolean changed =
+                (wasStaticIp != mUseStaticIp) ||
+                    (wasStaticIp && (
+                        oIp   != mDhcpInfo.ipAddress ||
+                        oGw   != mDhcpInfo.gateway ||
+                        oMsk  != mDhcpInfo.netmask ||
+                        oDns1 != mDhcpInfo.dns1 ||
+                        oDns2 != mDhcpInfo.dns2));
+
+            if (changed) {
+                sendMessage(CMD_RECONFIGURE_IP);
+                mConfigChanged = true;
+            }
+        }
+    }
+
+    /**
+     * Whether to disable coexistence mode while obtaining IP address. This
+     * logic will return true only if the current bluetooth
+     * 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);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_PROPERTIES, mNetworkProperties);
+        if (bssid != null)
+            intent.putExtra(WifiManager.EXTRA_BSSID, bssid);
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void sendConfigChangeBroadcast() {
+        Intent intent = new Intent(WifiManager.CONFIG_CHANGED_ACTION);
+        intent.putExtra(WifiManager.EXTRA_NETWORK_PROPERTIES, mNetworkProperties);
+        mContext.sendBroadcast(intent);
+    }
+
+    private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) {
+        Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state);
+        if (failedAuth) {
+            intent.putExtra(
+                WifiManager.EXTRA_SUPPLICANT_ERROR,
+                WifiManager.ERROR_AUTHENTICATING);
+        }
+        mContext.sendStickyBroadcast(intent);
+    }
+
+    private void sendSupplicantConnectionChangedBroadcast(boolean connected) {
+        if (!ActivityManagerNative.isSystemReady()) return;
+
+        Intent intent = new Intent(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+        intent.putExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, connected);
+        mContext.sendBroadcast(intent);
+    }
+
+    /**
+     * Record the detailed state of a network.
+     * @param state the new @{code DetailedState}
+     */
+    private void setDetailedState(NetworkInfo.DetailedState state) {
+        Log.d(TAG, "setDetailed state, old ="
+                + mNetworkInfo.getDetailedState() + " and new state=" + state);
+        if (state != mNetworkInfo.getDetailedState()) {
+            mNetworkInfo.setDetailedState(state, null, null);
+        }
+    }
+
+    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 CMD_ENABLE_RSSI_POLL:
+                    mEnableRssiPolling = (message.arg1 == 1);
+                    mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL);
+                    break;
+                    /* Discard */
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_RECONFIGURE_IP:
+                case SUP_CONNECTION_EVENT:
+                case SUP_DISCONNECTION_EVENT:
+                case DRIVER_START_EVENT:
+                case DRIVER_STOP_EVENT:
+                case NETWORK_CONNECTION_EVENT:
+                case NETWORK_DISCONNECTION_EVENT:
+                case SCAN_RESULTS_EVENT:
+                case SUPPLICANT_STATE_CHANGE_EVENT:
+                case PASSWORD_MAY_BE_INCORRECT_EVENT:
+                case CMD_BLACKLIST_NETWORK:
+                case CMD_CLEAR_BLACKLIST:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_REQUEST_CM_WAKELOCK:
+                    break;
+                default:
+                    Log.e(TAG, "Error! unhandled message" + message);
+                    break;
+            }
+            return HANDLED;
+        }
+    }
+
+    class InitialState extends HierarchicalState {
+        @Override
+        //TODO: could move logging into a common class
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            // [31-8] Reserved for future use
+            // [7 - 0] HSM state change
+            // 50021 wifi_state_changed (custom|1|5)
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            if (WifiNative.isDriverLoaded()) {
+                transitionTo(mDriverLoadedState);
+            }
+            else {
+                transitionTo(mDriverUnloadedState);
+            }
+        }
+    }
+
+    class DriverLoadingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            final Message message = new Message();
+            message.copyFrom(getCurrentMessage());
+            /* TODO: add a timeout to fail when driver load is hung.
+             * Similarly for driver unload.
+             */
+            new Thread(new Runnable() {
+                public void run() {
+                    sWakeLock.acquire();
+                    //enabling state
+                    switch(message.arg1) {
+                        case WIFI_STATE_ENABLING:
+                            setWifiState(WIFI_STATE_ENABLING);
+                            break;
+                        case WIFI_AP_STATE_ENABLING:
+                            setWifiApState(WIFI_AP_STATE_ENABLING);
+                            break;
+                    }
+
+                    if(WifiNative.loadDriver()) {
+                        Log.d(TAG, "Driver load successful");
+                        sendMessage(CMD_LOAD_DRIVER_SUCCESS);
+                    } else {
+                        Log.e(TAG, "Failed to load driver!");
+                        switch(message.arg1) {
+                            case WIFI_STATE_ENABLING:
+                                setWifiState(WIFI_STATE_UNKNOWN);
+                                break;
+                            case WIFI_AP_STATE_ENABLING:
+                                setWifiApState(WIFI_AP_STATE_FAILED);
+                                break;
+                        }
+                        sendMessage(CMD_LOAD_DRIVER_FAILURE);
+                    }
+                    sWakeLock.release();
+                }
+            }).start();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_LOAD_DRIVER_SUCCESS:
+                    transitionTo(mDriverLoadedState);
+                    break;
+                case CMD_LOAD_DRIVER_FAILURE:
+                    transitionTo(mDriverFailedState);
+                    break;
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverLoadedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case CMD_UNLOAD_DRIVER:
+                    transitionTo(mDriverUnloadingState);
+                    break;
+                case CMD_START_SUPPLICANT:
+                    if(WifiNative.startSupplicant()) {
+                        Log.d(TAG, "Supplicant start successful");
+                        mWifiMonitor.startMonitoring();
+                        setWifiState(WIFI_STATE_ENABLED);
+                        transitionTo(mWaitForSupState);
+                    } else {
+                        Log.e(TAG, "Failed to start supplicant!");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0));
+                    }
+                    break;
+                case CMD_START_AP:
+                    try {
+                        nwService.startAccessPoint((WifiConfiguration) message.obj,
+                                    mInterfaceName,
+                                    SOFTAP_IFACE);
+                    } catch(Exception e) {
+                        Log.e(TAG, "Exception in startAccessPoint()");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_FAILED, 0));
+                        break;
+                    }
+                    Log.d(TAG, "Soft AP start successful");
+                    setWifiApState(WIFI_AP_STATE_ENABLED);
+                    transitionTo(mSoftApStartedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverUnloadingState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+
+            final Message message = new Message();
+            message.copyFrom(getCurrentMessage());
+            new Thread(new Runnable() {
+                public void run() {
+                    if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+                    sWakeLock.acquire();
+                    if(WifiNative.unloadDriver()) {
+                        Log.d(TAG, "Driver unload successful");
+                        sendMessage(CMD_UNLOAD_DRIVER_SUCCESS);
+
+                        switch(message.arg1) {
+                            case WIFI_STATE_DISABLED:
+                            case WIFI_STATE_UNKNOWN:
+                                setWifiState(message.arg1);
+                                break;
+                            case WIFI_AP_STATE_DISABLED:
+                            case WIFI_AP_STATE_FAILED:
+                                setWifiApState(message.arg1);
+                                break;
+                        }
+                    } else {
+                        Log.e(TAG, "Failed to unload driver!");
+                        sendMessage(CMD_UNLOAD_DRIVER_FAILURE);
+
+                        switch(message.arg1) {
+                            case WIFI_STATE_DISABLED:
+                            case WIFI_STATE_UNKNOWN:
+                                setWifiState(WIFI_STATE_UNKNOWN);
+                                break;
+                            case WIFI_AP_STATE_DISABLED:
+                            case WIFI_AP_STATE_FAILED:
+                                setWifiApState(WIFI_AP_STATE_FAILED);
+                                break;
+                        }
+                    }
+                    sWakeLock.release();
+                }
+            }).start();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_UNLOAD_DRIVER_SUCCESS:
+                    transitionTo(mDriverUnloadedState);
+                    break;
+                case CMD_UNLOAD_DRIVER_FAILURE:
+                    transitionTo(mDriverFailedState);
+                    break;
+                case CMD_LOAD_DRIVER:
+                case CMD_UNLOAD_DRIVER:
+                case CMD_START_SUPPLICANT:
+                case CMD_STOP_SUPPLICANT:
+                case CMD_START_AP:
+                case CMD_STOP_AP:
+                case CMD_START_DRIVER:
+                case CMD_STOP_DRIVER:
+                case CMD_SET_SCAN_MODE:
+                case CMD_SET_SCAN_TYPE:
+                case CMD_SET_POWER_MODE:
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                case CMD_START_PACKET_FILTERING:
+                case CMD_STOP_PACKET_FILTERING:
+                    deferMessage(message);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverUnloadedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch (message.what) {
+                case CMD_LOAD_DRIVER:
+                    transitionTo(mDriverLoadingState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+    }
+
+    class DriverFailedState extends HierarchicalState {
+        @Override
+        public void enter() {
+            Log.e(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            return NOT_HANDLED;
+        }
+    }
+
+
+    class WaitForSupState extends HierarchicalState {
+        @Override
+        public void enter() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            switch(message.what) {
+                case SUP_CONNECTION_EVENT:
+                    Log.d(TAG, "Supplicant connection established");
+                    mSupplicantStateTracker.resetSupplicantState();
+                    /* Initialize data structures */
+                    mLastBssid = null;
+                    mLastNetworkId = -1;
+                    mLastSignalLevel = -1;
+
+                    mWifiInfo.setMacAddress(WifiNative.getMacAddressCommand());
+
+                    //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);
+                    WifiNative.closeSupplicantConnection();
+                    handleNetworkDisconnect();
+                    sendSupplicantConnectionChangedBroadcast(false);
+                    mSupplicantStateTracker.resetSupplicantState();
+                    transitionTo(mDriverLoadedState);
+
+                    /* When supplicant dies, unload driver and enter failed state */
+                    //TODO: consider bringing up supplicant again
+                    if (message.what == SUP_DISCONNECTION_EVENT) {
+                        Log.d(TAG, "Supplicant died, unloading driver");
+                        sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_UNKNOWN, 0));
+                    }
+                    break;
+                case CMD_START_DRIVER:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    WifiNative.startDriverCommand();
+                    transitionTo(mDriverStartingState);
+                    break;
+                case SCAN_RESULTS_EVENT:
+                    setScanResults(WifiNative.scanResultsCommand());
+                    sendScanResultsAvailableBroadcast();
+                    break;
+                case CMD_PING_SUPPLICANT:
+                    syncParams = (SyncParams) message.obj;
+                    syncParams.mSyncReturn.boolValue = WifiNative.pingCommand();
+                    notifyOnMsgObject(message);
+                    break;
+                case CMD_ADD_OR_UPDATE_NETWORK:
+                    EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+                    syncParams = (SyncParams) message.obj;
+                    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);
+                /* If supplicant has already connected, before we could finish establishing
+                 * the control channel connection, we miss all the supplicant events.
+                 * Disconnect and reconnect when driver has started to ensure we receive
+                 * all supplicant events.
+                 *
+                 * TODO: This is a bit unclean, ideally the supplicant should never
+                 * connect until told to do so by the framework
+                 */
+                WifiNative.disconnectCommand();
+                WifiNative.reconnectCommand();
+                transitionTo(mConnectModeState);
+            }
+        }
+        @Override
+        public boolean processMessage(Message message) {
+            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+            SyncParams syncParams;
+            switch(message.what) {
+                case CMD_SET_SCAN_TYPE:
+                    if (message.arg1 == SCAN_ACTIVE) {
+                        WifiNative.setScanModeCommand(true);
+                    } else {
+                        WifiNative.setScanModeCommand(false);
+                    }
+                    break;
+                case CMD_SET_POWER_MODE:
+                    WifiNative.setPowerModeCommand(message.arg1);
+                    break;
+                case CMD_SET_BLUETOOTH_COEXISTENCE:
+                    WifiNative.setBluetoothCoexistenceModeCommand(message.arg1);
+                    break;
+                case CMD_SET_BLUETOOTH_SCAN_MODE:
+                    WifiNative.setBluetoothCoexistenceScanModeCommand(message.arg1 == 1);
+                    break;
+                case CMD_SET_NUM_ALLOWED_CHANNELS:
+                    mNumAllowedChannels = message.arg1;
+                    WifiNative.setNumAllowedChannelsCommand(message.arg1);
+                    break;
+                case CMD_START_DRIVER:
+                    /* Ignore another driver start */
+                    break;
+                case CMD_STOP_DRIVER:
+                    WifiNative.stopDriverCommand();
+                    transitionTo(mDriverStoppingState);
+                    break;
+                case CMD_REQUEST_CM_WAKELOCK:
+                    if (mCm == null) {
+                        mCm = (ConnectivityManager)mContext.getSystemService(
+                                Context.CONNECTIVITY_SERVICE);
+                    }
+                    mCm.requestNetworkTransitionWakelock(TAG);
+                    break;
+                case CMD_START_PACKET_FILTERING:
+                    WifiNative.startPacketFiltering();
+                    break;
+                case CMD_STOP_PACKET_FILTERING:
+                    WifiNative.stopPacketFiltering();
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
+            return HANDLED;
+        }
+        @Override
+        public void exit() {
+            if (DBG) Log.d(TAG, getName() + "\n");
+            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());
+        }
+        @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;
+
+                    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);
+                  //TODO: we could also detect an IP config change
+                  // from a DHCP renewal and send out a config change
+                  // broadcast
+                  if (mConfigChanged) {
+                      sendConfigChangeBroadcast();
+                      mConfigChanged = false;
+                  }
+                  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 = new UninitializedState();
+        private HierarchicalState mInitializedState = new InitializedState();;
+        private HierarchicalState mInactiveState = new InactiveState();
+        private HierarchicalState mDisconnectState = new DisconnectedState();
+        private HierarchicalState mScanState = new ScanState();
+        private HierarchicalState mConnectState = new ConnectState();
+        private HierarchicalState mHandshakeState = new HandshakeState();
+        private HierarchicalState mCompletedState = new CompletedState();
+        private HierarchicalState mDormantState = new DormantState();
+
+        public SupplicantStateTracker(Context context, Handler target) {
+            super(TAG, target.getLooper());
+
+            addState(mUninitializedState);
+            addState(mInitializedState);
+                addState(mInactiveState, mInitializedState);
+                addState(mDisconnectState, mInitializedState);
+                addState(mScanState, mInitializedState);
+                addState(mConnectState, mInitializedState);
+                    addState(mHandshakeState, mConnectState);
+                    addState(mCompletedState, mConnectState);
+                addState(mDormantState, mInitializedState);
+
+            setInitialState(mUninitializedState);
+
+            //start the state machine
+            start();
+        }
+
+        public void handleEvent(StateChangeResult stateChangeResult) {
+            SupplicantState newState = (SupplicantState) stateChangeResult.state;
+
+            // Supplicant state change
+            // [31-13] Reserved for future use
+            // [8 - 0] Supplicant state (as defined in SupplicantState.java)
+            // 50023 supplicant_state_changed (custom|1|5)
+            EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, newState.ordinal());
+
+            sendMessage(obtainMessage(newState.ordinal(), stateChangeResult));
+        }
+
+        public void resetSupplicantState() {
+            transitionTo(mUninitializedState);
+        }
+
+        private void resetLoopDetection() {
+            mLoopDetectCount = 0;
+            mLoopDetectIndex = 0;
+        }
+
+        private boolean handleTransition(Message msg) {
+            if (DBG) Log.d(TAG, getName() + msg.toString() + "\n");
+            switch (msg.what) {
+                case DISCONNECTED:
+                    transitionTo(mDisconnectState);
+                    break;
+                case SCANNING:
+                    transitionTo(mScanState);
+                    break;
+                case ASSOCIATING:
+                    StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+                    /* BSSID is valid only in ASSOCIATING state */
+                    mWifiInfo.setBSSID(stateChangeResult.BSSID);
+                    //$FALL-THROUGH$
+                case ASSOCIATED:
+                case FOUR_WAY_HANDSHAKE:
+                case GROUP_HANDSHAKE:
+                    transitionTo(mHandshakeState);
+                    break;
+                case COMPLETED:
+                    transitionTo(mCompletedState);
+                    break;
+                case DORMANT:
+                    transitionTo(mDormantState);
+                    break;
+                case INACTIVE:
+                    transitionTo(mInactiveState);
+                    break;
+                case UNINITIALIZED:
+                case INVALID:
+                    transitionTo(mUninitializedState);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            StateChangeResult stateChangeResult = (StateChangeResult) msg.obj;
+            SupplicantState supState = (SupplicantState) stateChangeResult.state;
+            setDetailedState(WifiInfo.getDetailedStateOf(supState));
+            mWifiInfo.setSupplicantState(supState);
+            mWifiInfo.setNetworkId(stateChangeResult.networkId);
+            //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;
+            }
+            @Override
+            public void exit() {
+                mNetworkInfo.setIsAvailable(true);
+            }
+        }
+
+        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);
+            }
+            @Override
+            public void exit() {
+                mNetworkInfo.setIsAvailable(true);
+            }
+        }
+
+
+        class DisconnectedState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 resetLoopDetection();
+
+                 /* If a disconnect event happens after a password key failure
+                  * event, disable the network
+                  */
+                 if (mPasswordKeyMayBeIncorrect) {
+                     Log.d(TAG, "Failed to authenticate, disabling network " +
+                             mWifiInfo.getNetworkId());
+                     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;
+
+                 mPasswordKeyMayBeIncorrect = false;
+                 resetLoopDetection();
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+
+        class ConnectState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch (message.what) {
+                    case CMD_START_SCAN:
+                        WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE);
+                        WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class HandshakeState extends HierarchicalState {
+            @Override
+             public void enter() {
+                 if (DBG) Log.d(TAG, getName() + "\n");
+                 final Message message = getCurrentMessage();
+                 StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                 if (mLoopDetectIndex > message.what) {
+                     mLoopDetectCount++;
+                 }
+                 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+                     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;
+
+                 mRssiPollToken++;
+                 if (mEnableRssiPolling) {
+                     sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                             POLL_RSSI_INTERVAL_MSECS);
+                 }
+
+                 resetLoopDetection();
+
+                 mPasswordKeyMayBeIncorrect = false;
+                 sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+             }
+            @Override
+            public boolean processMessage(Message message) {
+                switch(message.what) {
+                    case ASSOCIATING:
+                    case ASSOCIATED:
+                    case FOUR_WAY_HANDSHAKE:
+                    case GROUP_HANDSHAKE:
+                    case COMPLETED:
+                        break;
+                    case CMD_RSSI_POLL:
+                        if (message.arg1 == mRssiPollToken) {
+                            // Get Info and continue polling
+                            requestPolledInfo();
+                            sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                    POLL_RSSI_INTERVAL_MSECS);
+                        } else {
+                            // Polling has completed
+                        }
+                        break;
+                    case CMD_ENABLE_RSSI_POLL:
+                        mRssiPollToken++;
+                        if (mEnableRssiPolling) {
+                            // first poll
+                            requestPolledInfo();
+                            sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0),
+                                    POLL_RSSI_INTERVAL_MSECS);
+                        }
+                        break;
+                    default:
+                        return handleTransition(message);
+                }
+                return HANDLED;
+            }
+        }
+
+        class DormantState extends HierarchicalState {
+            @Override
+            public void enter() {
+                if (DBG) Log.d(TAG, getName() + "\n");
+                Message message = getCurrentMessage();
+                StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+                resetLoopDetection();
+                mPasswordKeyMayBeIncorrect = false;
+
+                sendSupplicantStateChangedBroadcast(stateChangeResult, false);
+
+                /* TODO: reconnect is now being handled at DHCP failure handling
+                 * If we run into issues with staying in Dormant state, might
+                 * need a reconnect here
+                 */
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                return handleTransition(message);
+            }
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index 388beea..8e1b236 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -16,524 +16,59 @@
 
 package android.net.wifi;
 
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_STATE_UNKNOWN;
 
-/**
- * TODO: Add soft AP states as part of WIFI_STATE_XXX
- * Retain WIFI_STATE_ENABLING that indicates driver is loading
- * Add WIFI_STATE_AP_ENABLED to indicate soft AP has started
- * and WIFI_STATE_FAILED for failure
- * Deprecate WIFI_STATE_UNKNOWN
- *
- * Doing this will simplify the logic for sending broadcasts
- */
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
-import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED;
 
-import android.app.ActivityManagerNative;
-import android.net.NetworkInfo;
-import android.net.NetworkStateTracker;
-import android.net.DhcpInfo;
-import android.net.NetworkUtils;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
-import android.net.NetworkProperties;
-import android.os.Binder;
-import android.os.Message;
-import android.os.Parcelable;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.INetworkManagementService;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Process;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.Log;
-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;
-import android.content.ContentResolver;
-import android.content.Intent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.database.ContentObserver;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkProperties;
+import android.net.NetworkStateTracker;
+import android.os.Handler;
+import android.os.Message;
 
-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,
- * and all changes in connectivity state are initiated here.
+ * Track the state of wifi for connectivity service.
  *
  * @hide
  */
-//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 {
+public class WifiStateTracker implements NetworkStateTracker {
 
-    private static final String TAG = "WifiStateTracker";
     private static final String NETWORKTYPE = "WIFI";
-    private static final boolean DBG = false;
+    private static final String TAG = "WifiStateTracker";
 
-    /* TODO: fetch a configurable interface */
-    private static final String SOFTAP_IFACE = "wl0.1";
-
-    private WifiMonitor mWifiMonitor;
-    private INetworkManagementService nwService;
-    private ConnectivityManager mCm;
-
-    /* Scan results handling */
-    private List<ScanResult> mScanResults;
-    private static final Pattern scanResultPattern = Pattern.compile("\t+");
-    private static final int SCAN_RESULT_CACHE_SIZE = 80;
-    private final LinkedHashMap<String, ScanResult> mScanResultCache;
-
-    private String mInterfaceName;
     private 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 int mLastNetworkId;
-    private boolean mEnableRssiPolling = false;
-    private boolean mPasswordKeyMayBeIncorrect = false;
-    private boolean mUseStaticIp = false;
-    private int mReconnectCount = 0;
-    private boolean mIsScanMode = false;
-
-    /**
-     * Instance of the bluetooth headset helper. This needs to be created
-     * early because there is a delay before it actually 'connects', as
-     * noted by its javadoc. If we check before it is connected, it will be
-     * in an error state and we will not disable coexistence.
-     */
-    private BluetoothHeadset mBluetoothHeadset;
-
-    private BluetoothA2dp mBluetoothA2dp;
-
-    /**
-     * 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.
-     */
-    private static final int ICON_NETWORKS_AVAILABLE =
-            com.android.internal.R.drawable.stat_notify_wifi_in_range;
-    /**
-     * When a notification is shown, we wait this amount before possibly showing it again.
-     */
-    private final long NOTIFICATION_REPEAT_DELAY_MS;
-    /**
-     * Whether the user has set the setting to show the 'available networks' notification.
-     */
-    private boolean mNotificationEnabled;
-    /**
-     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
-     */
-    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
-    /**
-     * The {@link System#currentTimeMillis()} must be at least this value for us
-     * to show the notification again.
-     */
-    private long mNotificationRepeatTime;
-    /**
-     * The Notification object given to the NotificationManager.
-     */
-    private Notification mNotification;
-    /**
-     * Whether the notification is being shown, as set by us. That is, if the
-     * user cancels the notification, we will not receive the callback so this
-     * will still be true. We only guarantee if this is false, then the
-     * notification is not showing.
-     */
-    private boolean mNotificationShown;
-    /**
-     * The number of continuous scans that must occur before consider the
-     * supplicant in a scanning state. This allows supplicant to associate with
-     * remembered networks that are in the scan results.
-     */
-    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
-    /**
-     * The number of scans since the last network state change. When this
-     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
-     * supplicant to actually be scanning. When the network state changes to
-     * something other than scanning, we reset this to 0.
-     */
-    private int mNumScansSinceNetworkStateChange;
-
-    // Held during driver load and unload
-    private static PowerManager.WakeLock sWakeLock;
+    private NetworkInfo mNetworkInfo;
 
     /* 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 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},
-     *         {@link WifiManager#WIFI_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_STATE_ENABLED},
-     *         {@link WifiManager#WIFI_STATE_ENABLING},
-     *         {@link WifiManager#WIFI_STATE_UNKNOWN}
-     *
-     */
-    private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);
-
-    /**
-     * One of  {@link WifiManager#WIFI_AP_STATE_DISABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
-     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
-     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
-     *
-     */
-    private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED);
-
-    private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid());
-    private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid());
-
-    private final IBatteryStats mBatteryStats;
+    private BroadcastReceiver mWifiStateReceiver;
+    private WifiManager mWifiManager;
 
     public WifiStateTracker(Context context, Handler target) {
-        super(NETWORKTYPE);
-
         mCsHandler = target;
         mContext = context;
 
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, NETWORKTYPE, "");
-        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
-
-        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
-        nwService = INetworkManagementService.Stub.asInterface(b);
-
-        mWifiMonitor = new WifiMonitor(this);
-        mDhcpInfo = new DhcpInfo();
-        mWifiInfo = new WifiInfo();
-        mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0");
-        mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler());
-
-        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(),
-                Settings.Secure.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
-        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(target);
-        mNotificationEnabledSettingObserver.register();
-
-        mSettingsObserver = new SettingsObserver(new Handler());
-
-        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();
     }
 
 
-    /*********************************************************
-     * NetworkStateTracker interface implementation
-     ********************************************************/
-
     public void setTeardownRequested(boolean isRequested) {
         mTeardownRequested.set(isRequested);
     }
@@ -544,11 +79,17 @@
 
     /**
      * Begin monitoring wifi connectivity
-     * @deprecated
-     *
-     * TODO: remove this from callers
      */
     public void startMonitoring() {
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.CONFIG_CHANGED_ACTION);
+        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+        mWifiStateReceiver = new WifiStateReceiver();
+        mContext.registerReceiver(mWifiStateReceiver, filter);
     }
 
     /**
@@ -556,7 +97,8 @@
      * TODO: do away with return value after making MobileDataStateTracker async
      */
     public boolean teardown() {
-        sendMessage(CM_CMD_TEARDOWN);
+        mTeardownRequested.set(true);
+        mWifiManager.stopWifi();
         return true;
     }
 
@@ -565,7 +107,8 @@
      * TODO: do away with return value after making MobileDataStateTracker async
      */
     public boolean reconnect() {
-        sendMessage(CM_CMD_RECONNECT);
+        mTeardownRequested.set(false);
+        mWifiManager.startWifi();
         return true;
     }
 
@@ -575,7 +118,7 @@
      * TODO: do away with return value after making MobileDataStateTracker async
      */
     public boolean setRadio(boolean turnOn) {
-        setWifiEnabled(turnOn);
+        mWifiManager.setWifiEnabled(turnOn);
         return true;
     }
 
@@ -626,21 +169,6 @@
         return -1;
     }
 
-    /* TODO: will go away.
-     * Notifications are directly handled in WifiStateTracker at checkAndSetNotification()
-     */
-    public void interpretScanResultsAvailable() {
-
-    }
-
-    /**
-     * Return the name of our WLAN network interface.
-     * @return the name of our interface.
-     */
-    public String getInterfaceName() {
-        return mInterfaceName;
-    }
-
     /**
      * Check if private DNS route is set for the network
      */
@@ -698,3279 +226,23 @@
         return "net.tcp.buffersize.wifi";
     }
 
-
-    /*********************************************************
-     * Methods exposed for public use
-     ********************************************************/
-
-    /**
-     * TODO: doc
-     */
-    public boolean pingSupplicant() {
-        return sendSyncMessage(CMD_PING_SUPPLICANT).boolValue;
-    }
-
-    /**
-     * TODO: doc
-     */
-    public boolean startScan(boolean forceActive) {
-        return sendSyncMessage(obtainMessage(CMD_START_SCAN, forceActive ?
-                SCAN_ACTIVE : SCAN_PASSIVE, 0)).boolValue;
-    }
-
-    /**
-     * TODO: doc
-     */
-    public void setWifiEnabled(boolean enable) {
-        mLastEnableUid.set(Binder.getCallingUid());
-        if (enable) {
-            /* Argument is the state that is entered prior to load */
-            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
-            sendMessage(CMD_START_SUPPLICANT);
-        } else {
-            sendMessage(CMD_STOP_SUPPLICANT);
-            /* Argument is the state that is entered upon success */
-            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
-        }
-    }
-
-    /**
-     * TODO: doc
-     */
-    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) {
-        mLastApEnableUid.set(Binder.getCallingUid());
-        if (enable) {
-            /* Argument is the state that is entered prior to load */
-            sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_AP_STATE_ENABLING, 0));
-            sendMessage(obtainMessage(CMD_START_AP, wifiConfig));
-        } else {
-            sendMessage(CMD_STOP_AP);
-            /* Argument is the state that is entered upon success */
-            sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_AP_STATE_DISABLED, 0));
-        }
-    }
-
-    /**
-     * TODO: doc
-     */
-    public int 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
-     *
-     */
-    public WifiInfo requestConnectionInfo() {
-        return mWifiInfo;
-    }
-
-    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 {
-            setNumAllowedChannels(
-                    Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.WIFI_NUM_ALLOWED_CHANNELS));
-        } catch (Settings.SettingNotFoundException e) {
-            if (mNumAllowedChannels != 0) {
-                setNumAllowedChannels(mNumAllowedChannels);
-            }
-            // otherwise, use the driver default
-        }
-    }
-
-    /**
-     * Set the number of radio frequency channels that are allowed to be used
-     * in the current regulatory domain.
-     * @param numChannels the number of allowed channels. Must be greater than 0
-     * and less than or equal to 16.
-     */
-    public void setNumAllowedChannels(int numChannels) {
-        sendMessage(obtainMessage(CMD_SET_NUM_ALLOWED_CHANNELS, numChannels, 0));
-    }
-
-    /**
-     * Get number of allowed channels
-     *
-     * @return channel count, -1 on failure
-     *
-     * TODO: this is not a public API and needs to be removed in favor
-     * of asynchronous reporting. unused for now.
-     */
-    public int getNumAllowedChannels() {
-        return -1;
-    }
-
-    /**
-     * Set bluetooth coex mode:
-     *
-     * @param mode
-     *  BLUETOOTH_COEXISTENCE_MODE_ENABLED
-     *  BLUETOOTH_COEXISTENCE_MODE_DISABLED
-     *  BLUETOOTH_COEXISTENCE_MODE_SENSE
-     */
-    public void setBluetoothCoexistenceMode(int mode) {
-        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_COEXISTENCE, mode, 0));
-    }
-
-    /**
-     * Enable or disable Bluetooth coexistence scan mode. When this mode is on,
-     * some of the low-level scan parameters used by the driver are changed to
-     * reduce interference with A2DP streaming.
-     *
-     * @param isBluetoothPlaying whether to enable or disable this mode
-     */
-    public void setBluetoothScanMode(boolean isBluetoothPlaying) {
-        sendMessage(obtainMessage(CMD_SET_BLUETOOTH_SCAN_MODE, isBluetoothPlaying ? 1 : 0, 0));
-    }
-
-    /**
-     * Save configuration on supplicant
-     *
-     * @return {@code true} if the operation succeeds, {@code false} otherwise
-     *
-     * TODO: deprecate this
-     */
-    public boolean 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();
-        }
-    }
-
-    /**
-     * message.arg2 is reserved to indicate synchronized
-     * message.obj is used to store SyncParams
-     */
-    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;
-            }
-        }
-        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");
-        }
-
-        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);
-    }
-
-    /**
-     * Parse the scan result line passed to us by wpa_supplicant (helper).
-     * @param line the line to parse
-     * @return the {@link ScanResult} object
-     */
-    private ScanResult parseScanResult(String line) {
-        ScanResult scanResult = null;
-        if (line != null) {
-            /*
-             * Cache implementation (LinkedHashMap) is not synchronized, thus,
-             * must synchronized here!
-             */
-            synchronized (mScanResultCache) {
-                String[] result = scanResultPattern.split(line);
-                if (3 <= result.length && result.length <= 5) {
-                    String bssid = result[0];
-                    // bssid | frequency | level | flags | ssid
-                    int frequency;
-                    int level;
-                    try {
-                        frequency = Integer.parseInt(result[1]);
-                        level = Integer.parseInt(result[2]);
-                        /* some implementations avoid negative values by adding 256
-                         * so we need to adjust for that here.
-                         */
-                        if (level > 0) level -= 256;
-                    } catch (NumberFormatException e) {
-                        frequency = 0;
-                        level = 0;
-                    }
-
-                    /*
-                     * The formatting of the results returned by
-                     * wpa_supplicant is intended to make the fields
-                     * line up nicely when printed,
-                     * not to make them easy to parse. So we have to
-                     * apply some heuristics to figure out which field
-                     * is the SSID and which field is the flags.
-                     */
-                    String ssid;
-                    String flags;
-                    if (result.length == 4) {
-                        if (result[3].charAt(0) == '[') {
-                            flags = result[3];
-                            ssid = "";
-                        } else {
-                            flags = "";
-                            ssid = result[3];
-                        }
-                    } else if (result.length == 5) {
-                        flags = result[3];
-                        ssid = result[4];
-                    } else {
-                        // Here, we must have 3 fields: no flags and ssid
-                        // set
-                        flags = "";
-                        ssid = "";
-                    }
-
-                    // bssid + ssid is the hash key
-                    String key = bssid + ssid;
-                    scanResult = mScanResultCache.get(key);
-                    if (scanResult != null) {
-                        scanResult.level = level;
-                        scanResult.SSID = ssid;
-                        scanResult.capabilities = flags;
-                        scanResult.frequency = frequency;
-                    } else {
-                        // Do not add scan results that have no SSID set
-                        if (0 < ssid.trim().length()) {
-                            scanResult =
-                                new ScanResult(
-                                    ssid, bssid, flags, level, frequency);
-                            mScanResultCache.put(key, scanResult);
-                        }
-                    }
-                } else {
-                    Log.w(TAG, "Misformatted scan result text with " +
-                          result.length + " fields: " + line);
-                }
-            }
-        }
-
-        return scanResult;
-    }
-
-    /**
-     * scanResults input format
-     * 00:bb:cc:dd:cc:ee       2427    166     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net1
-     * 00:bb:cc:dd:cc:ff       2412    165     [WPA-EAP-TKIP][WPA2-EAP-CCMP]   Net2
-     */
-    private void setScanResults(String scanResults) {
-        if (scanResults == null) {
-            return;
-        }
-
-        List<ScanResult> scanList = new ArrayList<ScanResult>();
-
-        int lineCount = 0;
-
-        int scanResultsLen = scanResults.length();
-        // Parse the result string, keeping in mind that the last line does
-        // not end with a newline.
-        for (int lineBeg = 0, lineEnd = 0; lineEnd <= scanResultsLen; ++lineEnd) {
-            if (lineEnd == scanResultsLen || scanResults.charAt(lineEnd) == '\n') {
-                ++lineCount;
-
-                if (lineCount == 1) {
-                    lineBeg = lineEnd + 1;
-                    continue;
-                }
-                if (lineEnd > lineBeg) {
-                    String line = scanResults.substring(lineBeg, lineEnd);
-                    ScanResult scanResult = parseScanResult(line);
-                    if (scanResult != null) {
-                        scanList.add(scanResult);
-                    } else {
-                        Log.w(TAG, "misformatted scan result for: " + line);
-                    }
-                }
-                lineBeg = lineEnd + 1;
-            }
-        }
-
-        mScanResults = scanList;
-    }
-
-    private 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;
-                }
-            }
-        }
-
-        // 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 {
-            mNetworkProperties.setInterface(NetworkInterface.getByName(mInterfaceName));
-        } catch (SocketException e) {
-            Log.e(TAG, "SocketException creating NetworkInterface from " + mInterfaceName +
-                    ". e=" + e);
-            return;
-        } catch (NullPointerException e) {
-            Log.e(TAG, "NPE creating NetworkInterface. e=" + e);
-            return;
-        }
-        // TODO - fix this for v6
-        try {
-            mNetworkProperties.addAddress(InetAddress.getByAddress(
-                    NetworkUtils.v4IntToArray(mDhcpInfo.ipAddress)));
-        } catch (UnknownHostException e) {
-            Log.e(TAG, "Exception setting IpAddress using " + mDhcpInfo + ", e=" + e);
-        }
-
-        try {
-            mNetworkProperties.setGateway(InetAddress.getByAddress(NetworkUtils.v4IntToArray(
-                    mDhcpInfo.gateway)));
-        } catch (UnknownHostException e) {
-            Log.e(TAG, "Exception setting Gateway using " + mDhcpInfo + ", e=" + e);
-        }
-
-        try {
-            mNetworkProperties.addDns(InetAddress.getByAddress(
-                    NetworkUtils.v4IntToArray(mDhcpInfo.dns1)));
-        } catch (UnknownHostException e) {
-            Log.e(TAG, "Exception setting Dns1 using " + mDhcpInfo + ", e=" + e);
-        }
-        try {
-            mNetworkProperties.addDns(InetAddress.getByAddress(
-                    NetworkUtils.v4IntToArray(mDhcpInfo.dns2)));
-
-        } catch (UnknownHostException e) {
-            Log.e(TAG, "Exception setting Dns2 using " + mDhcpInfo + ", e=" + e);
-        }
-        // TODO - add proxy info
-    }
-
-
-    private void checkUseStaticIp() {
-        mUseStaticIp = false;
-        final ContentResolver cr = mContext.getContentResolver();
-        try {
-            if (Settings.System.getInt(cr, Settings.System.WIFI_USE_STATIC_IP) == 0) {
-                return;
-            }
-        } catch (Settings.SettingNotFoundException e) {
-            return;
-        }
-
-        try {
-            String addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_IP);
-            if (addr != null) {
-                mDhcpInfo.ipAddress = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_GATEWAY);
-            if (addr != null) {
-                mDhcpInfo.gateway = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_NETMASK);
-            if (addr != null) {
-                mDhcpInfo.netmask = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS1);
-            if (addr != null) {
-                mDhcpInfo.dns1 = stringToIpAddr(addr);
-            } else {
-                return;
-            }
-            addr = Settings.System.getString(cr, Settings.System.WIFI_STATIC_DNS2);
-            if (addr != null) {
-                mDhcpInfo.dns2 = stringToIpAddr(addr);
-            } else {
-                mDhcpInfo.dns2 = 0;
-            }
-        } catch (UnknownHostException e) {
-            return;
-        }
-        mUseStaticIp = true;
-    }
-
-    private static int stringToIpAddr(String addrString) throws UnknownHostException {
-        try {
-            String[] parts = addrString.split("\\.");
-            if (parts.length != 4) {
-                throw new UnknownHostException(addrString);
-            }
-
-            int a = Integer.parseInt(parts[0])      ;
-            int b = Integer.parseInt(parts[1]) <<  8;
-            int c = Integer.parseInt(parts[2]) << 16;
-            int d = Integer.parseInt(parts[3]) << 24;
-
-            return a | b | c | d;
-        } catch (NumberFormatException ex) {
-            throw new UnknownHostException(addrString);
-        }
-    }
-
-    private int getMaxDhcpRetries() {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                                      Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
-                                      DEFAULT_MAX_DHCP_RETRIES);
-    }
-
-    private class SettingsObserver extends ContentObserver {
-        public SettingsObserver(Handler handler) {
-            super(handler);
-            ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_USE_STATIC_IP), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_IP), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_GATEWAY), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_NETMASK), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_DNS1), false, this);
-            cr.registerContentObserver(Settings.System.getUriFor(
-                Settings.System.WIFI_STATIC_DNS2), false, this);
-        }
-
+    private class WifiStateReceiver extends BroadcastReceiver {
         @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            boolean wasStaticIp = mUseStaticIp;
-            int oIp, oGw, oMsk, oDns1, oDns2;
-            oIp = oGw = oMsk = oDns1 = oDns2 = 0;
-            if (wasStaticIp) {
-                oIp = mDhcpInfo.ipAddress;
-                oGw = mDhcpInfo.gateway;
-                oMsk = mDhcpInfo.netmask;
-                oDns1 = mDhcpInfo.dns1;
-                oDns2 = mDhcpInfo.dns2;
-            }
-            checkUseStaticIp();
-
-            if (mWifiInfo.getSupplicantState() == SupplicantState.UNINITIALIZED) {
-                return;
-            }
-
-            boolean changed =
-                (wasStaticIp != mUseStaticIp) ||
-                    (wasStaticIp && (
-                        oIp   != mDhcpInfo.ipAddress ||
-                        oGw   != mDhcpInfo.gateway ||
-                        oMsk  != mDhcpInfo.netmask ||
-                        oDns1 != mDhcpInfo.dns1 ||
-                        oDns2 != mDhcpInfo.dns2));
-
-            if (changed) {
-                sendMessage(CMD_RECONFIGURE_IP);
-                if (mUseStaticIp) {
-                    mCsHandler.sendEmptyMessage(EVENT_CONFIGURATION_CHANGED);
-                }
+        public void onReceive(Context context, Intent intent) {
+           if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                        WifiManager.EXTRA_NETWORK_INFO);
+                mNetworkProperties = (NetworkProperties) intent.getParcelableExtra(
+                        WifiManager.EXTRA_NETWORK_PROPERTIES);
+                Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
+                msg.sendToTarget();
+            } else if (intent.getAction().equals(WifiManager.CONFIG_CHANGED_ACTION)) {
+                mNetworkProperties = (NetworkProperties) intent.getParcelableExtra(
+                        WifiManager.EXTRA_NETWORK_PROPERTIES);
+                Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
+                msg.sendToTarget();
             }
         }
     }
 
-
-    /**
-     * 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);
-                /* If supplicant has already connected, before we could finish establishing
-                 * the control channel connection, we miss all the supplicant events.
-                 * Disconnect and reconnect when driver has started to ensure we receive
-                 * all supplicant events.
-                 *
-                 * TODO: This is a bit unclean, ideally the supplicant should never
-                 * connect until told to do so by the framework
-                 */
-                WifiNative.disconnectCommand();
-                WifiNative.reconnectCommand();
-                transitionTo(mConnectModeState);
-            }
-        }
-        @Override
-        public boolean processMessage(Message message) {
-            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
-            SyncParams syncParams;
-            switch(message.what) {
-                case CMD_SET_SCAN_TYPE:
-                    if (message.arg1 == SCAN_ACTIVE) {
-                        WifiNative.setScanModeCommand(true);
-                    } else {
-                        WifiNative.setScanModeCommand(false);
-                    }
-                    break;
-                case CMD_SET_POWER_MODE:
-                    WifiNative.setPowerModeCommand(message.arg1);
-                    break;
-                case CMD_SET_BLUETOOTH_COEXISTENCE:
-                    WifiNative.setBluetoothCoexistenceModeCommand(message.arg1);
-                    break;
-                case CMD_SET_BLUETOOTH_SCAN_MODE:
-                    WifiNative.setBluetoothCoexistenceScanModeCommand(message.arg1 == 1);
-                    break;
-                case CMD_SET_NUM_ALLOWED_CHANNELS:
-                    mNumAllowedChannels = message.arg1;
-                    WifiNative.setNumAllowedChannelsCommand(message.arg1);
-                    break;
-                case CMD_START_DRIVER:
-                    /* Ignore another driver start */
-                    break;
-                case CMD_STOP_DRIVER:
-                    WifiNative.stopDriverCommand();
-                    transitionTo(mDriverStoppingState);
-                    break;
-                case CMD_REQUEST_CM_WAKELOCK:
-                    if (mCm == null) {
-                        mCm = (ConnectivityManager)mContext.getSystemService(
-                                Context.CONNECTIVITY_SERVICE);
-                    }
-                    mCm.requestNetworkTransitionWakelock(TAG);
-                    break;
-                case CMD_START_PACKET_FILTERING:
-                    WifiNative.startPacketFiltering();
-                    break;
-                case CMD_STOP_PACKET_FILTERING:
-                    WifiNative.stopPacketFiltering();
-                    break;
-                default:
-                    return NOT_HANDLED;
-            }
-            EventLog.writeEvent(EVENTLOG_WIFI_EVENT_HANDLED, message.what);
-            return HANDLED;
-        }
-        @Override
-        public void exit() {
-            if (DBG) Log.d(TAG, getName() + "\n");
-            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) {
-            super(handler);
-        }
-
-        public void register() {
-            ContentResolver cr = mContext.getContentResolver();
-            cr.registerContentObserver(Settings.Secure.getUriFor(
-                Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
-            mNotificationEnabled = getValue();
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            mNotificationEnabled = getValue();
-            if (!mNotificationEnabled) {
-                // Remove any notification that may be showing
-                setNotificationVisible(false, 0, true, 0);
-            }
-
-            resetNotificationTimer();
-        }
-
-        private boolean getValue() {
-            return Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
-        }
-    }
-}
+}
\ No newline at end of file