am e0c127a4: am 3240339c: Merge "Add error checks to the copyFrom functions." into honeycomb

* commit 'e0c127a4a7cb499246eb8dbc966ca421919786ec':
  Add error checks to the copyFrom functions.
diff --git a/api/current.xml b/api/current.xml
index 6387ef9..65f9530 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -22813,6 +22813,19 @@
 <parameter name="id" type="int">
 </parameter>
 </method>
+<method name="dispatchGenericMotionEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="ev" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="dispatchKeyEvent"
  return="boolean"
  abstract="false"
@@ -23643,6 +23656,19 @@
  visibility="public"
 >
 </method>
+<method name="onGenericMotionEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="onKeyDown"
  return="boolean"
  abstract="false"
@@ -27571,6 +27597,19 @@
  visibility="public"
 >
 </method>
+<method name="dispatchGenericMotionEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="ev" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="dispatchKeyEvent"
  return="boolean"
  abstract="false"
@@ -27926,6 +27965,19 @@
  visibility="public"
 >
 </method>
+<method name="onGenericMotionEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="onKeyDown"
  return="boolean"
  abstract="false"
@@ -194305,10 +194357,10 @@
  synchronized="false"
  static="true"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
-<parameter name="addr" type="int">
+<parameter name="ipv4Address" type="int">
 </parameter>
 </method>
 <method name="formatShortFileSize"
@@ -207259,6 +207311,17 @@
  visibility="public"
 >
 </field>
+<field name="SOURCE_CLASS_JOYSTICK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="SOURCE_CLASS_MASK"
  type="int"
  transient="false"
@@ -207314,6 +207377,28 @@
  visibility="public"
 >
 </field>
+<field name="SOURCE_GAMEPAD"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1025"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="SOURCE_JOYSTICK"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16777232"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="SOURCE_KEYBOARD"
  type="int"
  transient="false"
@@ -209156,6 +209241,182 @@
  visibility="public"
 >
 </field>
+<field name="KEYCODE_BUTTON_1"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="188"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_10"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="197"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_11"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="198"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_12"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="199"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_13"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="200"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_14"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="201"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_15"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="202"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_16"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="203"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_2"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="189"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_3"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="190"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_4"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="191"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_5"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="192"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_6"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="193"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_7"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="194"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_8"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="195"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KEYCODE_BUTTON_9"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="196"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="KEYCODE_BUTTON_A"
  type="int"
  transient="false"
@@ -216273,6 +216534,19 @@
 <parameter name="canvas" type="android.graphics.Canvas">
 </parameter>
 </method>
+<method name="dispatchGenericMotionEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="dispatchKeyEvent"
  return="boolean"
  abstract="false"
@@ -218415,6 +218689,19 @@
 <parameter name="previouslyFocusedRect" type="android.graphics.Rect">
 </parameter>
 </method>
+<method name="onGenericMotionEvent"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="onKeyDown"
  return="boolean"
  abstract="false"
@@ -225352,6 +225639,19 @@
 <parameter name="hardwareAccelerated" type="boolean">
 </parameter>
 </method>
+<method name="superDispatchGenericMotionEvent"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="superDispatchKeyEvent"
  return="boolean"
  abstract="true"
@@ -225697,6 +225997,19 @@
  deprecated="not deprecated"
  visibility="public"
 >
+<method name="dispatchGenericMotionEvent"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.MotionEvent">
+</parameter>
+</method>
 <method name="dispatchKeyEvent"
  return="boolean"
  abstract="true"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6f0cb45..3eef785 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2081,7 +2081,39 @@
     public boolean onTrackballEvent(MotionEvent event) {
         return false;
     }
-    
+
+    /**
+     * Called when a generic motion event was not handled by any of the
+     * views inside of the activity.
+     * <p>
+     * Generic motion events are dispatched to the focused view to describe
+     * the motions of input devices such as joysticks.  The
+     * {@link MotionEvent#getSource() source} of the motion event specifies
+     * the class of input that was received.  Implementations of this method
+     * must examine the bits in the source before processing the event.
+     * The following code example shows how this is done.
+     * </p>
+     * <code>
+     * public boolean onGenericMotionEvent(MotionEvent event) {
+     *     if ((event.getSource() &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+     *         float x = event.getX();
+     *         float y = event.getY();
+     *         // process the joystick motion
+     *         return true;
+     *     }
+     *     return super.onGenericMotionEvent(event);
+     * }
+     * </code>
+     *
+     * @param event The generic motion event being processed.
+     *
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
     /**
      * Called whenever a key, touch, or trackball event is dispatched to the
      * activity.  Implement this method if you wish to know that the user has
@@ -2264,6 +2296,24 @@
         return onTrackballEvent(ev);
     }
 
+    /**
+     * Called to process generic motion events.  You can override this to
+     * intercept all generic motion events before they are dispatched to the
+     * window.  Be sure to call this implementation for generic motion events
+     * that should be handled normally.
+     *
+     * @param ev The generic motion event.
+     *
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        onUserInteraction();
+        if (getWindow().superDispatchGenericMotionEvent(ev)) {
+            return true;
+        }
+        return onGenericMotionEvent(ev);
+    }
+
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         event.setClassName(getClass().getName());
         event.setPackageName(getPackageName());
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 6791400..b0929dd 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -633,7 +633,39 @@
     public boolean onTrackballEvent(MotionEvent event) {
         return false;
     }
-    
+
+    /**
+     * Called when a generic motion event was not handled by any of the
+     * views inside of the dialog.
+     * <p>
+     * Generic motion events are dispatched to the focused view to describe
+     * the motions of input devices such as joysticks.  The
+     * {@link MotionEvent#getSource() source} of the motion event specifies
+     * the class of input that was received.  Implementations of this method
+     * must examine the bits in the source before processing the event.
+     * The following code example shows how this is done.
+     * </p>
+     * <code>
+     * public boolean onGenericMotionEvent(MotionEvent event) {
+     *     if ((event.getSource() &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+     *         float x = event.getX();
+     *         float y = event.getY();
+     *         // process the joystick motion
+     *         return true;
+     *     }
+     *     return super.onGenericMotionEvent(event);
+     * }
+     * </code>
+     *
+     * @param event The generic motion event being processed.
+     *
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
     public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
         if (mDecor != null) {
             mWindowManager.updateViewLayout(mDecor, params);
@@ -722,6 +754,23 @@
         return onTrackballEvent(ev);
     }
 
+    /**
+     * Called to process generic motion events.  You can override this to
+     * intercept all generic motion events before they are dispatched to the
+     * window.  Be sure to call this implementation for generic motion events
+     * that should be handled normally.
+     *
+     * @param ev The generic motion event.
+     *
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        if (mWindow.superDispatchGenericMotionEvent(ev)) {
+            return true;
+        }
+        return onGenericMotionEvent(ev);
+    }
+
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         event.setClassName(getClass().getName());
         event.setPackageName(mContext.getPackageName());
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 2fa2834..f45cf2a 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -1373,6 +1373,7 @@
         public void handleMessage(Message msg) {
             long earliestFuturePollTime = Long.MAX_VALUE;
             long nextPendingSyncTime = Long.MAX_VALUE;
+
             // Setting the value here instead of a method because we want the dumpsys logs
             // to have the most recent value used.
             try {
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7676258..e8292cc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3085,11 +3085,7 @@
         if (!sCompatibilityModeEnabled) {
             ai.disableCompatibilityMode();
         }
-        if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
-            ai.enabled = true;
-        } else if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
-            ai.enabled = false;
-        }
+        ai.enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
         return ai;
     }
 
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 8a653dd..f1bf852 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -125,24 +125,19 @@
 
     /**
      * Convert a IPv4 address from an integer to an InetAddress.
-     * @param hostAddr is an Int corresponding to the IPv4 address in network byte order
-     * @return the IP address as an {@code InetAddress}, returns null if
-     * unable to convert or if the int is an invalid address.
+     * @param hostAddress an int corresponding to the IPv4 address in network byte order
      */
     public static InetAddress intToInetAddress(int hostAddress) {
-        InetAddress inetAddress;
         byte[] addressBytes = { (byte)(0xff & hostAddress),
                                 (byte)(0xff & (hostAddress >> 8)),
                                 (byte)(0xff & (hostAddress >> 16)),
                                 (byte)(0xff & (hostAddress >> 24)) };
 
         try {
-           inetAddress = InetAddress.getByAddress(addressBytes);
-        } catch(UnknownHostException e) {
-           return null;
+           return InetAddress.getByAddress(addressBytes);
+        } catch (UnknownHostException e) {
+           throw new AssertionError();
         }
-
-        return inetAddress;
     }
 
     /**
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index baaa3ce..5ae65df 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -17,32 +17,33 @@
 package android.text.format;
 
 import android.content.Context;
+import android.net.NetworkUtils;
 
 /**
  * Utility class to aid in formatting common values that are not covered
- * by the standard java.util.Formatter.
+ * by {@link java.util.Formatter}
  */
 public final class Formatter {
 
     /**
      * Formats a content size to be in the form of bytes, kilobytes, megabytes, etc
-     * 
+     *
      * @param context Context to use to load the localized units
-     * @param number size value to be formated
-     * @return formated string with the number
+     * @param number size value to be formatted
+     * @return formatted string with the number
      */
     public static String formatFileSize(Context context, long number) {
         return formatFileSize(context, number, false);
     }
-    
+
     /**
      * Like {@link #formatFileSize}, but trying to generate shorter numbers
-     * (showing fewer digits of precisin).
+     * (showing fewer digits of precision).
      */
     public static String formatShortFileSize(Context context, long number) {
         return formatFileSize(context, number, true);
     }
-    
+
     private static String formatFileSize(Context context, long number, boolean shorter) {
         if (context == null) {
             return "";
@@ -92,21 +93,21 @@
             getString(com.android.internal.R.string.fileSizeSuffix,
                       value, context.getString(suffix));
     }
-    
+
     /**
      * Returns a string in the canonical IP format ###.###.###.### from a packed integer containing
      * the IP address.  The IP address is expected to be in little-endian format (LSB first). That
      * is, 0x01020304 will return "4.3.2.1".
-     * 
-     * @param addr the IP address as a packed integer with LSB first.
+     *
+     * @param ipv4Address the IP address as a packed integer with LSB first.
      * @return string with canonical IP address format.
+     *
+     * @deprecated this method doesn't support IPv6 addresses. Prefer {@link
+     *     java.net.InetAddress#getHostAddress()}, which supports both IPv4 and
+     *     IPv6 addresses.
      */
-    public static String formatIpAddress(int addr) {
-        StringBuffer buf = new StringBuffer();
-        buf.append(addr  & 0xff).append('.').
-            append((addr >>>= 8) & 0xff).append('.').
-            append((addr >>>= 8) & 0xff).append('.').
-            append((addr >>>= 8) & 0xff);
-        return buf.toString();
+    @Deprecated
+    public static String formatIpAddress(int ipv4Address) {
+        return NetworkUtils.intToInetAddress(ipv4Address).getHostAddress();
     }
 }
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index dd04975..e799f76 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -98,7 +98,16 @@
      * Use {@link #getMotionRange} to query the range of positions.
      */
     public static final int SOURCE_CLASS_POSITION = 0x00000008;
-    
+
+    /**
+     * The input source is a joystick.
+     *
+     * A {@link MotionEvent} should be interpreted as absolute joystick movements.
+     *
+     * Use {@link #getMotionRange} to query the range of positions.
+     */
+    public static final int SOURCE_CLASS_JOYSTICK = 0x00000010;
+
     /**
      * The input source is unknown.
      */
@@ -117,7 +126,15 @@
      * @see #SOURCE_CLASS_BUTTON
      */
     public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON;
-    
+
+    /**
+     * The input source is a game pad.
+     * (It may also be a {@link #SOURCE_JOYSTICK}).
+     *
+     * @see #SOURCE_CLASS_BUTTON
+     */
+    public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON;
+
     /**
      * The input source is a touch screen pointing device.
      * 
@@ -148,7 +165,15 @@
      * @see #SOURCE_CLASS_POSITION
      */
     public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION;
-    
+
+    /**
+     * The input source is a joystick.
+     * (It may also be a {@link #SOURCE_GAMEPAD}).
+     *
+     * @see #SOURCE_CLASS_JOYSTICK
+     */
+    public static final int SOURCE_JOYSTICK = 0x01000000 | SOURCE_CLASS_JOYSTICK;
+
     /**
      * A special input source constant that is used when filtering input devices
      * to match devices that provide any type of input source.
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index ecf1aef..695d16a 100755
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -533,8 +533,40 @@
     /** Key code constant: App switch key.
      * Should bring up the application switcher dialog. */
     public static final int KEYCODE_APP_SWITCH      = 187;
+    /** Key code constant: Generic Game Pad Button #1.*/
+    public static final int KEYCODE_BUTTON_1        = 188;
+    /** Key code constant: Generic Game Pad Button #2.*/
+    public static final int KEYCODE_BUTTON_2        = 189;
+    /** Key code constant: Generic Game Pad Button #3.*/
+    public static final int KEYCODE_BUTTON_3        = 190;
+    /** Key code constant: Generic Game Pad Button #4.*/
+    public static final int KEYCODE_BUTTON_4        = 191;
+    /** Key code constant: Generic Game Pad Button #5.*/
+    public static final int KEYCODE_BUTTON_5        = 192;
+    /** Key code constant: Generic Game Pad Button #6.*/
+    public static final int KEYCODE_BUTTON_6        = 193;
+    /** Key code constant: Generic Game Pad Button #7.*/
+    public static final int KEYCODE_BUTTON_7        = 194;
+    /** Key code constant: Generic Game Pad Button #8.*/
+    public static final int KEYCODE_BUTTON_8        = 195;
+    /** Key code constant: Generic Game Pad Button #9.*/
+    public static final int KEYCODE_BUTTON_9        = 196;
+    /** Key code constant: Generic Game Pad Button #10.*/
+    public static final int KEYCODE_BUTTON_10       = 197;
+    /** Key code constant: Generic Game Pad Button #11.*/
+    public static final int KEYCODE_BUTTON_11       = 198;
+    /** Key code constant: Generic Game Pad Button #12.*/
+    public static final int KEYCODE_BUTTON_12       = 199;
+    /** Key code constant: Generic Game Pad Button #13.*/
+    public static final int KEYCODE_BUTTON_13       = 200;
+    /** Key code constant: Generic Game Pad Button #14.*/
+    public static final int KEYCODE_BUTTON_14       = 201;
+    /** Key code constant: Generic Game Pad Button #15.*/
+    public static final int KEYCODE_BUTTON_15       = 202;
+    /** Key code constant: Generic Game Pad Button #16.*/
+    public static final int KEYCODE_BUTTON_16       = 203;
 
-    private static final int LAST_KEYCODE           = KEYCODE_APP_SWITCH;
+    private static final int LAST_KEYCODE           = KEYCODE_BUTTON_16;
 
     // NOTE: If you add a new keycode here you must also add it to:
     //  isSystem()
@@ -741,6 +773,22 @@
         "KEYCODE_PROG_YELLOW",
         "KEYCODE_PROG_BLUE",
         "KEYCODE_APP_SWITCH",
+        "KEYCODE_BUTTON_1",
+        "KEYCODE_BUTTON_2",
+        "KEYCODE_BUTTON_3",
+        "KEYCODE_BUTTON_4",
+        "KEYCODE_BUTTON_5",
+        "KEYCODE_BUTTON_6",
+        "KEYCODE_BUTTON_7",
+        "KEYCODE_BUTTON_8",
+        "KEYCODE_BUTTON_9",
+        "KEYCODE_BUTTON_10",
+        "KEYCODE_BUTTON_11",
+        "KEYCODE_BUTTON_12",
+        "KEYCODE_BUTTON_13",
+        "KEYCODE_BUTTON_14",
+        "KEYCODE_BUTTON_15",
+        "KEYCODE_BUTTON_16",
     };
 
     // Symbolic names of all metakeys in bit order from least significant to most significant.
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index db2cd50..0a5c5c6 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -26,7 +26,8 @@
  * class may hold either absolute or relative movements, depending on what
  * it is being used for.
  * <p>
- * On pointing devices such as touch screens, pointer coordinates specify absolute
+ * On pointing devices with source class {@link InputDevice#SOURCE_CLASS_POINTER}
+ * such as touch screens, the pointer coordinates specify absolute
  * positions such as view X/Y coordinates.  Each complete gesture is represented
  * by a sequence of motion events with actions that describe pointer state transitions
  * and movements.  A gesture starts with a motion event with {@link #ACTION_DOWN}
@@ -38,11 +39,18 @@
  * by a motion event with {@link #ACTION_UP} or when gesture is canceled
  * with {@link #ACTION_CANCEL}.
  * </p><p>
- * On trackballs, the pointer coordinates specify relative movements as X/Y deltas.
+ * On trackball devices with source class {@link InputDevice#SOURCE_CLASS_TRACKBALL},
+ * the pointer coordinates specify relative movements as X/Y deltas.
  * A trackball gesture consists of a sequence of movements described by motion
  * events with {@link #ACTION_MOVE} interspersed with occasional {@link #ACTION_DOWN}
  * or {@link #ACTION_UP} motion events when the trackball button is pressed or released.
  * </p><p>
+ * On joystick devices with source class {@link InputDevice#SOURCE_CLASS_JOYSTICK},
+ * the pointer coordinates specify the absolute position of the joystick axes.
+ * The joystick axis values are normalized to a range of -1.0 to 1.0 where 0.0 corresponds
+ * to the center position.  More information about the set of available axes and the
+ * range of motion can be obtained using {@link InputDevice#getMotionRange}.
+ * </p><p>
  * Motion events always report movements for all pointers at once.  The number
  * of pointers only ever changes by one as individual pointers go up and down,
  * except when the gesture is canceled.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 53fc0c0..e127d5c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4493,6 +4493,16 @@
     }
 
     /**
+     * Pass a generic motion event down to the focused view.
+     *
+     * @param event The motion event to be dispatched.
+     * @return True if the event was handled by the view, false otherwise.
+     */
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        return onGenericMotionEvent(event);
+    }
+
+    /**
      * Called when the window containing this view gains or loses window focus.
      * ViewGroups should override to route to their children.
      *
@@ -4994,6 +5004,37 @@
     }
 
     /**
+     * Implement this method to handle generic motion events.
+     * <p>
+     * Generic motion events are dispatched to the focused view to describe
+     * the motions of input devices such as joysticks.  The
+     * {@link MotionEvent#getSource() source} of the motion event specifies
+     * the class of input that was received.  Implementations of this method
+     * must examine the bits in the source before processing the event.
+     * The following code example shows how this is done.
+     * </p>
+     * <code>
+     * public boolean onGenericMotionEvent(MotionEvent event) {
+     *     if ((event.getSource() &amp; InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
+     *         float x = event.getX();
+     *         float y = event.getY();
+     *         // process the joystick motion
+     *         return true;
+     *     }
+     *     return super.onGenericMotionEvent(event);
+     * }
+     * </code>
+     *
+     * @param event The generic motion event being processed.
+     *
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
      * Implement this method to handle touch screen motion events.
      *
      * @param event The motion event.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d1781cc..4f68f79 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1124,6 +1124,19 @@
      * {@inheritDoc}
      */
     @Override
+    public boolean dispatchGenericMotionEvent(MotionEvent event) {
+        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
+            return super.dispatchGenericMotionEvent(event);
+        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
+            return mFocused.dispatchGenericMotionEvent(event);
+        }
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (!onFilterTouchEventForSecurity(ev)) {
             return false;
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 96f8cdc..2e64cd0 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -128,6 +128,11 @@
     final TrackballAxis mTrackballAxisX = new TrackballAxis();
     final TrackballAxis mTrackballAxisY = new TrackballAxis();
 
+    int mLastJoystickXDirection;
+    int mLastJoystickYDirection;
+    int mLastJoystickXKeyCode;
+    int mLastJoystickYKeyCode;
+
     final int[] mTmpLocation = new int[2];
 
     final TypedValue mTmpValue = new TypedValue();
@@ -1888,6 +1893,7 @@
     public final static int CLOSE_SYSTEM_DIALOGS = 1014;
     public final static int DISPATCH_DRAG_EVENT = 1015;
     public final static int DISPATCH_DRAG_LOCATION_EVENT = 1016;
+    public final static int DISPATCH_GENERIC_MOTION = 1017;
 
     @Override
     public void handleMessage(Message msg) {
@@ -1924,6 +1930,9 @@
         case DISPATCH_TRACKBALL:
             deliverTrackballEvent((MotionEvent) msg.obj, msg.arg1 != 0);
             break;
+        case DISPATCH_GENERIC_MOTION:
+            deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0);
+            break;
         case DISPATCH_APP_VISIBILITY:
             handleAppVisibility(msg.arg1 != 0);
             break;
@@ -2430,6 +2439,102 @@
         }
     }
 
+    private void deliverGenericMotionEvent(MotionEvent event, boolean sendDone) {
+        final int source = event.getSource();
+        final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0;
+
+        // If there is no view, then the event will not be handled.
+        if (mView == null || !mAdded) {
+            if (isJoystick) {
+                updateJoystickDirection(event, false);
+            }
+            finishGenericMotionEvent(event, sendDone, false);
+            return;
+        }
+
+        // Deliver the event to the view.
+        if (mView.dispatchGenericMotionEvent(event)) {
+            ensureTouchMode(false);
+            if (isJoystick) {
+                updateJoystickDirection(event, false);
+            }
+            finishGenericMotionEvent(event, sendDone, true);
+            return;
+        }
+
+        if (isJoystick) {
+            // Translate the joystick event into DPAD keys and try to deliver those.
+            updateJoystickDirection(event, true);
+            finishGenericMotionEvent(event, sendDone, true);
+        } else {
+            finishGenericMotionEvent(event, sendDone, false);
+        }
+    }
+
+    private void finishGenericMotionEvent(MotionEvent event, boolean sendDone, boolean handled) {
+        event.recycle();
+        if (sendDone) {
+            finishInputEvent(handled);
+        }
+    }
+
+    private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) {
+        final long time = event.getEventTime();
+        final int metaState = event.getMetaState();
+        final int deviceId = event.getDeviceId();
+        final int source = event.getSource();
+        final int xDirection = joystickAxisValueToDirection(event.getX());
+        final int yDirection = joystickAxisValueToDirection(event.getY());
+
+        if (xDirection != mLastJoystickXDirection) {
+            if (mLastJoystickXKeyCode != 0) {
+                deliverKeyEvent(new KeyEvent(time, time,
+                        KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+                mLastJoystickXKeyCode = 0;
+            }
+
+            mLastJoystickXDirection = xDirection;
+
+            if (xDirection != 0 && synthesizeNewKeys) {
+                mLastJoystickXKeyCode = xDirection > 0
+                        ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
+                deliverKeyEvent(new KeyEvent(time, time,
+                        KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+            }
+        }
+
+        if (yDirection != mLastJoystickYDirection) {
+            if (mLastJoystickYKeyCode != 0) {
+                deliverKeyEvent(new KeyEvent(time, time,
+                        KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+                mLastJoystickYKeyCode = 0;
+            }
+
+            mLastJoystickYDirection = yDirection;
+
+            if (yDirection != 0 && synthesizeNewKeys) {
+                mLastJoystickYKeyCode = yDirection > 0
+                        ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
+                deliverKeyEvent(new KeyEvent(time, time,
+                        KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
+                        deviceId, 0, KeyEvent.FLAG_FALLBACK, source), false);
+            }
+        }
+    }
+
+    private static int joystickAxisValueToDirection(float value) {
+        if (value >= 0.5f) {
+            return 1;
+        } else if (value <= -0.5f) {
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
     /**
      * Returns true if the key is used for keyboard navigation.
      * @param keyEvent The key event.
@@ -3063,11 +3168,7 @@
         } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
             dispatchTrackball(event, sendDone);
         } else {
-            // TODO
-            Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);
-            if (sendDone) {
-                finishInputEvent(false);
-            }
+            dispatchGenericMotion(event, sendDone);
         }
     }
 
@@ -3092,7 +3193,14 @@
         msg.arg1 = sendDone ? 1 : 0;
         sendMessageAtTime(msg, event.getEventTime());
     }
-    
+
+    private void dispatchGenericMotion(MotionEvent event, boolean sendDone) {
+        Message msg = obtainMessage(DISPATCH_GENERIC_MOTION);
+        msg.obj = event;
+        msg.arg1 = sendDone ? 1 : 0;
+        sendMessageAtTime(msg, event.getEventTime());
+    }
+
     public void dispatchAppVisibility(boolean visible) {
         Message msg = obtainMessage(DISPATCH_APP_VISIBILITY);
         msg.arg1 = visible ? 1 : 0;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 2f27935..5cbaf2a 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -185,6 +185,18 @@
         public boolean dispatchTrackballEvent(MotionEvent event);
 
         /**
+         * Called to process generic motion events.  At the very least your
+         * implementation must call
+         * {@link android.view.Window#superDispatchGenericMotionEvent} to do the
+         * standard processing.
+         *
+         * @param event The generic motion event.
+         *
+         * @return boolean Return true if this event was consumed.
+         */
+        public boolean dispatchGenericMotionEvent(MotionEvent event);
+
+        /**
          * Called to process population of {@link AccessibilityEvent}s.
          *
          * @param event The event.
@@ -1063,6 +1075,14 @@
     public abstract boolean superDispatchTrackballEvent(MotionEvent event);
     
     /**
+     * Used by custom windows, such as Dialog, to pass the generic motion event
+     * further down the view hierarchy. Application developers should
+     * not need to implement or call this.
+     *
+     */
+    public abstract boolean superDispatchGenericMotionEvent(MotionEvent event);
+
+    /**
      * Retrieve the top-level window decor view (containing the standard
      * window frame/decorations and the client's content inside of that), which
      * can be added as a window to the window manager.
diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java
index 6095a64..2a76e33 100755
--- a/core/java/android/view/WindowOrientationListener.java
+++ b/core/java/android/view/WindowOrientationListener.java
@@ -234,14 +234,6 @@
         // high time constant.
         private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
 
-        // Minimum acceleration considered, in m/s^2. Below this threshold sensor noise will have
-        // significant impact on the calculations and in case of the vector (0, 0, 0) there is no
-        // defined rotation or tilt at all. Low or zero readings can happen when space travelling
-        // or free falling, but more commonly when shaking or getting bad readings from the sensor.
-        // The accelerometer is turned off when not used and polling it too soon after it is
-        // turned on may result in (0, 0, 0).
-        private static final float MIN_ABS_ACCELERATION = 1.5f;
-
         // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL.  There's no
         // way to get this information from SensorManager.
         // Note the actual period is generally 3-30ms larger than this depending on the device, but
@@ -355,9 +347,6 @@
             float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
 
             handleAccelerationDistrust(deviation);
-            if (magnitude < MIN_ABS_ACCELERATION) {
-                return; // Ignore tilt and orientation when (0, 0, 0) or low reading
-            }
 
             // only filter tilt when we're accelerating
             float alpha = 1;
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 19e2b8d..3f02b0b 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1310,6 +1310,22 @@
         <enum name="KEYCODE_PROG_YELLOW" value="185" />
         <enum name="KEYCODE_PROG_BLUE" value="186" />
         <enum name="KEYCODE_APP_SWITCH" value="187" />
+        <enum name="KEYCODE_BUTTON_1" value="188" />
+        <enum name="KEYCODE_BUTTON_2" value="189" />
+        <enum name="KEYCODE_BUTTON_3" value="190" />
+        <enum name="KEYCODE_BUTTON_4" value="191" />
+        <enum name="KEYCODE_BUTTON_5" value="192" />
+        <enum name="KEYCODE_BUTTON_6" value="193" />
+        <enum name="KEYCODE_BUTTON_7" value="194" />
+        <enum name="KEYCODE_BUTTON_8" value="195" />
+        <enum name="KEYCODE_BUTTON_9" value="196" />
+        <enum name="KEYCODE_BUTTON_10" value="197" />
+        <enum name="KEYCODE_BUTTON_11" value="198" />
+        <enum name="KEYCODE_BUTTON_12" value="199" />
+        <enum name="KEYCODE_BUTTON_13" value="200" />
+        <enum name="KEYCODE_BUTTON_14" value="201" />
+        <enum name="KEYCODE_BUTTON_15" value="202" />
+        <enum name="KEYCODE_BUTTON_16" value="203" />
     </attr>
 
     <!-- ***************************************************************** -->
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
index 43cf06a..96b028a 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
@@ -19,6 +19,16 @@
 import android.content.Context;
 import android.test.InstrumentationTestCase;
 
+/**
+ * Stress test suite for Bluetooth related functions.
+ *
+ * Includes tests for enabling/disabling bluetooth, enabling/disabling discoverable mode,
+ * starting/stopping scans, connecting/disconnecting to HFP, A2DP, HID, PAN profiles, and verifying
+ * that remote connections/disconnections occur for the PAN profile.
+ * <p>
+ * This test suite uses {@link android.bluetooth.BluetoothTestRunner} to for parameters such as the
+ * number of iterations and the addresses of remote Bluetooth devices.
+ */
 public class BluetoothStressTest extends InstrumentationTestCase {
     private static final String TAG = "BluetoothStressTest";
     private static final String OUTPUT_FILE = "BluetoothStressTestOutput.txt";
@@ -40,6 +50,9 @@
         mTestUtils.close();
     }
 
+    /**
+     * Stress test for enabling and disabling Bluetooth.
+     */
     public void testEnable() {
         int iterations = BluetoothTestRunner.sEnableIterations;
         if (iterations == 0) {
@@ -55,6 +68,9 @@
         }
     }
 
+    /**
+     * Stress test for putting the device in and taking the device out of discoverable mode.
+     */
     public void testDiscoverable() {
         int iterations = BluetoothTestRunner.sDiscoverableIterations;
         if (iterations == 0) {
@@ -73,6 +89,9 @@
         mTestUtils.disable(adapter);
     }
 
+    /**
+     * Stress test for starting and stopping Bluetooth scans.
+     */
     public void testScan() {
         int iterations = BluetoothTestRunner.sScanIterations;
         if (iterations == 0) {
@@ -91,6 +110,30 @@
         mTestUtils.disable(adapter);
     }
 
+    /**
+     * Stress test for enabling and disabling the PAN NAP profile.
+     */
+    public void testEnablePan() {
+        int iterations = BluetoothTestRunner.sEnablePanIterations;
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        mTestUtils.enable(adapter);
+
+        for (int i = 0; i < iterations; i++) {
+            mTestUtils.writeOutput("testEnablePan iteration " + (i + 1) + " of "
+                    + iterations);
+            mTestUtils.enablePan(adapter);
+            mTestUtils.disablePan(adapter);
+        }
+
+        mTestUtils.disable(adapter);
+    }
+
+    /**
+     * Stress test for pairing and unpairing with a remote device.
+     * <p>
+     * In this test, the local device initiates pairing with a remote device, and then unpairs with
+     * the device after the pairing has successfully completed.
+     */
     public void testPair() {
         int iterations = BluetoothTestRunner.sPairIterations;
         if (iterations == 0) {
@@ -110,6 +153,12 @@
         mTestUtils.disable(adapter);
     }
 
+    /**
+     * Stress test for accepting a pairing request and unpairing with a remote device.
+     * <p>
+     * In this test, the local device waits for a pairing request from a remote device.  It accepts
+     * the request and then unpairs after the paring has successfully completed.
+     */
     public void testAcceptPair() {
         int iterations = BluetoothTestRunner.sPairIterations;
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -125,6 +174,12 @@
         mTestUtils.disable(adapter);
     }
 
+    /**
+     * Stress test for connecting and disconnecting with an A2DP source.
+     * <p>
+     * In this test, the local device plays the role of an A2DP sink, and initiates connections and
+     * disconnections with an A2DP source.
+     */
     public void testConnectA2dp() {
         int iterations = BluetoothTestRunner.sConnectA2dpIterations;
         if (iterations == 0) {
@@ -143,10 +198,16 @@
             mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.A2DP);
         }
 
-        // TODO: Unpair from device if device can accept pairing after unpairing
+        mTestUtils.unpair(adapter, device);
         mTestUtils.disable(adapter);
     }
 
+    /**
+     * Stress test for connecting and disconnecting the HFP with a hands free device.
+     * <p>
+     * In this test, the local device plays the role of an HFP audio gateway, and initiates
+     * connections and disconnections with a hands free device.
+     */
     public void testConnectHeadset() {
         int iterations = BluetoothTestRunner.sConnectHeadsetIterations;
         if (iterations == 0) {
@@ -165,7 +226,94 @@
             mTestUtils.disconnectProfile(adapter, device, BluetoothProfile.HEADSET);
         }
 
-        // TODO: Unpair from device if device can accept pairing after unpairing
+        mTestUtils.unpair(adapter, device);
+        mTestUtils.disable(adapter);
+    }
+
+    /**
+     * Stress test for connecting and disconnecting with a HID device.
+     * <p>
+     * In this test, the local device plays the role of a HID host, and initiates connections and
+     * disconnections with a HID device.
+     */
+    public void testConnectInput() {
+        int iterations = BluetoothTestRunner.sConnectInputIterations;
+        if (iterations == 0) {
+            return;
+        }
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sInputAddress);
+        mTestUtils.enable(adapter);
+        mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey,
+                BluetoothTestRunner.sPairPin);
+
+        for (int i = 0; i < iterations; i++) {
+            mTestUtils.writeOutput("connectInput iteration " + (i + 1) + " of " + iterations);
+            mTestUtils.connectInput(adapter, device);
+            mTestUtils.disconnectInput(adapter, device);
+        }
+
+        mTestUtils.unpair(adapter, device);
+        mTestUtils.disable(adapter);
+    }
+
+    /**
+     * Stress test for connecting and disconnecting with a PAN NAP.
+     * <p>
+     * In this test, the local device plays the role of a PANU, and initiates connections and
+     * disconnections with a NAP.
+     */
+    public void testConnectPan() {
+        int iterations = BluetoothTestRunner.sConnectPanIterations;
+        if (iterations == 0) {
+            return;
+        }
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sPanAddress);
+        mTestUtils.enable(adapter);
+        mTestUtils.pair(adapter, device, BluetoothTestRunner.sPairPasskey,
+                BluetoothTestRunner.sPairPin);
+
+        for (int i = 0; i < iterations; i++) {
+            mTestUtils.writeOutput("connectPan iteration " + (i + 1) + " of " + iterations);
+            mTestUtils.connectPan(adapter, device);
+            mTestUtils.disconnectPan(adapter, device);
+        }
+
+        mTestUtils.unpair(adapter, device);
+        mTestUtils.disable(adapter);
+    }
+
+    /**
+     * Stress test for verifying a PANU connecting and disconnecting with the device.
+     * <p>
+     * In this test, the local device plays the role of a NAP which a remote PANU connects and
+     * disconnects from.
+     */
+    public void testIncomingPanConnection() {
+        int iterations = BluetoothTestRunner.sConnectPanIterations;
+        if (iterations == 0) {
+            return;
+        }
+
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sPanAddress);
+        mTestUtils.enable(adapter);
+        mTestUtils.enablePan(adapter);
+        mTestUtils.acceptPair(adapter, device, BluetoothTestRunner.sPairPasskey,
+                BluetoothTestRunner.sPairPin);
+
+        for (int i = 0; i < iterations; i++) {
+            mTestUtils.writeOutput("incomingPanConnection iteration " + (i + 1) + " of "
+                    + iterations);
+            mTestUtils.incomingPanConnection(adapter, device);
+            mTestUtils.incomingPanDisconnection(adapter, device);
+        }
+
+        mTestUtils.unpair(adapter, device);
+        mTestUtils.disablePan(adapter);
         mTestUtils.disable(adapter);
     }
 }
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
index 3e589fc..cede05a 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
@@ -23,19 +23,51 @@
 import android.test.InstrumentationTestSuite;
 import android.util.Log;
 
+/**
+ * Instrumentation test runner for Bluetooth tests.
+ * <p>
+ * To run:
+ * <pre>
+ * {@code
+ * adb shell am instrument \
+ *     [-e enable_iterations <iterations>] \
+ *     [-e discoverable_iterations <iterations>] \
+ *     [-e scan_iterations <iterations>] \
+ *     [-e enable_pan_iterations <iterations>] \
+ *     [-e pair_iterations <iterations>] \
+ *     [-e connect_a2dp_iterations <iterations>] \
+ *     [-e connect_headset_iterations <iterations>] \
+ *     [-e connect_input_iterations <iterations>] \
+ *     [-e connect_pan_iterations <iterations>] \
+ *     [-e pair_address <address>] \
+ *     [-e headset_address <address>] \
+ *     [-e a2dp_address <address>] \
+ *     [-e input_address <address>] \
+ *     [-e pan_address <address>] \
+ *     [-e pair_pin <pin>] \
+ *     [-e pair_passkey <passkey>] \
+ *     -w com.android.frameworks.coretests/android.bluetooth.BluetoothTestRunner
+ * }
+ * </pre>
+ */
 public class BluetoothTestRunner extends InstrumentationTestRunner {
     private static final String TAG = "BluetoothTestRunner";
 
     public static int sEnableIterations = 100;
     public static int sDiscoverableIterations = 1000;
     public static int sScanIterations = 1000;
+    public static int sEnablePanIterations = 1000;
     public static int sPairIterations = 100;
     public static int sConnectHeadsetIterations = 100;
     public static int sConnectA2dpIterations = 100;
+    public static int sConnectInputIterations = 100;
+    public static int sConnectPanIterations = 100;
 
     public static String sPairAddress = "";
     public static String sHeadsetAddress = "";
     public static String sA2dpAddress = "";
+    public static String sInputAddress = "";
+    public static String sPanAddress = "";
 
     public static byte[] sPairPin = {'1', '2', '3', '4'};
     public static int sPairPasskey = 123456;
@@ -81,6 +113,15 @@
             }
         }
 
+        val = arguments.getString("enable_pan_iterations");
+        if (val != null) {
+            try {
+                sEnablePanIterations = Integer.parseInt(val);
+            } catch (NumberFormatException e) {
+                // Invalid argument, fall back to default value
+            }
+        }
+
         val = arguments.getString("pair_iterations");
         if (val != null) {
             try {
@@ -108,6 +149,24 @@
             }
         }
 
+        val = arguments.getString("connect_input_iterations");
+        if (val != null) {
+            try {
+                sConnectInputIterations = Integer.parseInt(val);
+            } catch (NumberFormatException e) {
+                // Invalid argument, fall back to default value
+            }
+        }
+
+        val = arguments.getString("connect_pan_iterations");
+        if (val != null) {
+            try {
+                sConnectPanIterations = Integer.parseInt(val);
+            } catch (NumberFormatException e) {
+                // Invalid argument, fall back to default value
+            }
+        }
+
         val = arguments.getString("pair_address");
         if (val != null) {
             sPairAddress = val;
@@ -123,6 +182,16 @@
             sA2dpAddress = val;
         }
 
+        val = arguments.getString("input_address");
+        if (val != null) {
+            sInputAddress = val;
+        }
+
+        val = arguments.getString("pan_address");
+        if (val != null) {
+            sPanAddress = val;
+        }
+
         val = arguments.getString("pair_pin");
         if (val != null) {
             sPairPin = BluetoothDevice.convertPinToBytes(val);
@@ -143,9 +212,13 @@
         Log.i(TAG, String.format("pair_iterations=%d", sPairIterations));
         Log.i(TAG, String.format("connect_a2dp_iterations=%d", sConnectA2dpIterations));
         Log.i(TAG, String.format("connect_headset_iterations=%d", sConnectHeadsetIterations));
+        Log.i(TAG, String.format("connect_input_iterations=%d", sConnectInputIterations));
+        Log.i(TAG, String.format("connect_pan_iterations=%d", sConnectPanIterations));
         Log.i(TAG, String.format("pair_address=%s", sPairAddress));
         Log.i(TAG, String.format("a2dp_address=%s", sA2dpAddress));
         Log.i(TAG, String.format("headset_address=%s", sHeadsetAddress));
+        Log.i(TAG, String.format("input_address=%s", sInputAddress));
+        Log.i(TAG, String.format("pan_address=%s", sPanAddress));
         Log.i(TAG, String.format("pair_pin=%s", new String(sPairPin)));
         Log.i(TAG, String.format("pair_passkey=%d", sPairPasskey));
 
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
index 6da38a7..effed76 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
@@ -35,49 +35,29 @@
 public class BluetoothTestUtils extends Assert {
 
     /**
-     * Timeout for {@link BluetoothAdapter#disable()} in ms.
+     * Timeout for enable/disable in ms.
      */
-    private static final int DISABLE_TIMEOUT = 20000;
+    private static final int ENABLE_DISABLE_TIMEOUT = 20000;
 
     /**
-     * Timeout for {@link BluetoothAdapter#enable()} in ms.
+     * Timeout for discoverable/undiscoverable in ms.
      */
-    private static final int ENABLE_TIMEOUT = 20000;
+    private static final int DISCOVERABLE_UNDISCOVERABLE_TIMEOUT = 5000;
 
     /**
-     * Timeout for {@link BluetoothAdapter#setScanMode(int)} in ms.
+     * Timeout for starting/stopping a scan in ms.
      */
-    private static final int SET_SCAN_MODE_TIMEOUT = 5000;
+    private static final int START_STOP_SCAN_TIMEOUT = 5000;
 
     /**
-     * Timeout for {@link BluetoothAdapter#startDiscovery()} in ms.
+     * Timeout for pair/unpair in ms.
      */
-    private static final int START_DISCOVERY_TIMEOUT = 5000;
+    private static final int PAIR_UNPAIR_TIMEOUT = 20000;
 
     /**
-     * Timeout for {@link BluetoothAdapter#cancelDiscovery()} in ms.
+     * Timeout for connecting/disconnecting a profile in ms.
      */
-    private static final int CANCEL_DISCOVERY_TIMEOUT = 5000;
-
-    /**
-     * Timeout for {@link BluetoothDevice#createBond()} in ms.
-     */
-    private static final int PAIR_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothDevice#removeBond()} in ms.
-     */
-    private static final int UNPAIR_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothProfile#connect(BluetoothDevice)} in ms.
-     */
-    private static final int CONNECT_PROFILE_TIMEOUT = 20000;
-
-    /**
-     * Timeout for {@link BluetoothProfile#disconnect(BluetoothDevice)} in ms.
-     */
-    private static final int DISCONNECT_PROFILE_TIMEOUT = 20000;
+    private static final int CONNECT_DISCONNECT_PROFILE_TIMEOUT = 20000;
 
     /**
      * Timeout to connect a profile proxy in ms.
@@ -265,7 +245,6 @@
 
         @Override
         public void onReceive(Context context, Intent intent) {
-
             if (mConnectionAction != null && mConnectionAction.equals(intent.getAction())) {
                 if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) {
                     return;
@@ -291,6 +270,91 @@
         }
     }
 
+    private class ConnectInputReceiver extends FlagReceiver {
+        private static final int STATE_DISCONNECTED_FLAG = 1;
+        private static final int STATE_CONNECTING_FLAG = 1 << 1;
+        private static final int STATE_CONNECTED_FLAG = 1 << 2;
+        private static final int STATE_DISCONNECTING_FLAG = 1 << 3;
+
+        private BluetoothDevice mDevice;
+
+        public ConnectInputReceiver(BluetoothDevice device, int expectedFlags) {
+            super(expectedFlags);
+
+            mDevice = device;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))) {
+                return;
+            }
+
+            if (BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED.equals(intent.getAction())) {
+                int state = intent.getIntExtra(BluetoothInputDevice.EXTRA_INPUT_DEVICE_STATE, -1);
+                assertNotSame(-1, state);
+                switch (state) {
+                    case BluetoothInputDevice.STATE_DISCONNECTED:
+                        setFiredFlag(STATE_DISCONNECTED_FLAG);
+                        break;
+                    case BluetoothInputDevice.STATE_CONNECTING:
+                        setFiredFlag(STATE_CONNECTING_FLAG);
+                        break;
+                    case BluetoothInputDevice.STATE_CONNECTED:
+                        setFiredFlag(STATE_CONNECTED_FLAG);
+                        break;
+                    case BluetoothInputDevice.STATE_DISCONNECTING:
+                        setFiredFlag(STATE_DISCONNECTING_FLAG);
+                        break;
+                }
+            }
+        }
+    }
+
+    private class ConnectPanReceiver extends FlagReceiver {
+        private static final int STATE_DISCONNECTED_FLAG = 1;
+        private static final int STATE_CONNECTING_FLAG = 1 << 1;
+        private static final int STATE_CONNECTED_FLAG = 1 << 2;
+        private static final int STATE_DISCONNECTING_FLAG = 1 << 3;
+
+        private BluetoothDevice mDevice;
+        private int mRole;
+
+        public ConnectPanReceiver(BluetoothDevice device, int role, int expectedFlags) {
+            super (expectedFlags);
+
+            mDevice = device;
+            mRole = role;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!mDevice.equals(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))
+                    || mRole != intent.getIntExtra(BluetoothPan.EXTRA_LOCAL_ROLE, -1)) {
+                return;
+            }
+
+            if (BluetoothPan.ACTION_PAN_STATE_CHANGED.equals(intent.getAction())) {
+                int state = intent.getIntExtra(BluetoothPan.EXTRA_PAN_STATE, -1);
+                assertNotSame(-1, state);
+                switch (state) {
+                    case BluetoothPan.STATE_DISCONNECTED:
+                        setFiredFlag(STATE_DISCONNECTED_FLAG);
+                        break;
+                    case BluetoothPan.STATE_CONNECTING:
+                        setFiredFlag(STATE_CONNECTING_FLAG);
+                        break;
+                    case BluetoothPan.STATE_CONNECTED:
+                        setFiredFlag(STATE_CONNECTED_FLAG);
+                        break;
+                    case BluetoothPan.STATE_DISCONNECTING:
+                        setFiredFlag(STATE_DISCONNECTING_FLAG);
+                        break;
+                }
+            }
+        }
+    }
+
     private BluetoothProfile.ServiceListener mServiceListener =
             new BluetoothProfile.ServiceListener() {
         public void onServiceConnected(int profile, BluetoothProfile proxy) {
@@ -330,10 +394,24 @@
     private BluetoothA2dp mA2dp;
     private BluetoothHeadset mHeadset;
 
+    /**
+     * Creates a utility instance for testing Bluetooth.
+     *
+     * @param context The context of the application using the utility.
+     * @param tag The log tag of the application using the utility.
+     */
     public BluetoothTestUtils(Context context, String tag) {
         this(context, tag, null);
     }
 
+    /**
+     * Creates a utility instance for testing Bluetooth.
+     *
+     * @param context The context of the application using the utility.
+     * @param tag The log tag of the application using the utility.
+     * @param outputFile The path to an output file if the utility is to write results to a
+     *        separate file.
+     */
     public BluetoothTestUtils(Context context, String tag, String outputFile) {
         mContext = context;
         mTag = tag;
@@ -352,6 +430,9 @@
         }
     }
 
+    /**
+     * Closes the utility instance and unregisters any BroadcastReceivers.
+     */
     public void close() {
         while (!mReceivers.isEmpty()) {
             mContext.unregisterReceiver(mReceivers.remove(0));
@@ -366,6 +447,12 @@
         }
     }
 
+    /**
+     * Enables Bluetooth and checks to make sure that Bluetooth was turned on and that the correct
+     * actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     */
     public void enable(BluetoothAdapter adapter) {
         int mask = (BluetoothReceiver.STATE_TURNING_ON_FLAG | BluetoothReceiver.STATE_ON_FLAG
                 | BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG);
@@ -397,22 +484,19 @@
         }
 
         long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < ENABLE_TIMEOUT) {
+        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
             state = adapter.getState();
-            if (state == BluetoothAdapter.STATE_ON) {
+            if (state == BluetoothAdapter.STATE_ON
+                    && (receiver.getFiredFlags() & mask) == mask) {
                 assertTrue(adapter.isEnabled());
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    long finish = receiver.getCompletedTime();
-                    if (start != -1 && finish != -1) {
-                        writeOutput(String.format("enable() completed in %d ms", (finish - start)));
-                    } else {
-                        writeOutput("enable() completed");
-                    }
-                    removeReceiver(receiver);
-                    return;
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("enable() completed in %d ms", (finish - start)));
+                } else {
+                    writeOutput("enable() completed");
                 }
-            } else {
-                assertEquals(BluetoothAdapter.STATE_TURNING_ON, state);
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -423,6 +507,12 @@
                 state, BluetoothAdapter.STATE_ON, firedFlags, mask));
     }
 
+    /**
+     * Disables Bluetooth and checks to make sure that Bluetooth was turned off and that the correct
+     * actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     */
     public void disable(BluetoothAdapter adapter) {
         int mask = (BluetoothReceiver.STATE_TURNING_OFF_FLAG | BluetoothReceiver.STATE_OFF_FLAG
                 | BluetoothReceiver.SCAN_MODE_NONE_FLAG);
@@ -454,23 +544,19 @@
         }
 
         long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < DISABLE_TIMEOUT) {
+        while (System.currentTimeMillis() - s < ENABLE_DISABLE_TIMEOUT) {
             state = adapter.getState();
-            if (state == BluetoothAdapter.STATE_OFF) {
+            if (state == BluetoothAdapter.STATE_OFF
+                    && (receiver.getFiredFlags() & mask) == mask) {
                 assertFalse(adapter.isEnabled());
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    long finish = receiver.getCompletedTime();
-                    if (start != -1 && finish != -1) {
-                        writeOutput(String.format("disable() completed in %d ms",
-                                (finish - start)));
-                    } else {
-                        writeOutput("disable() completed");
-                    }
-                    removeReceiver(receiver);
-                    return;
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("disable() completed in %d ms", (finish - start)));
+                } else {
+                    writeOutput("disable() completed");
                 }
-            } else {
-                assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state);
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -481,6 +567,12 @@
                 state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
     }
 
+    /**
+     * Puts the local device into discoverable mode and checks to make sure that the local device
+     * is in discoverable mode and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     */
     public void discoverable(BluetoothAdapter adapter) {
         int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
 
@@ -499,17 +591,14 @@
         long start = System.currentTimeMillis();
         assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
 
-        while (System.currentTimeMillis() - start < SET_SCAN_MODE_TIMEOUT) {
+        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
             scanMode = adapter.getScanMode();
-            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    writeOutput(String.format("discoverable() completed in %d ms",
-                            (receiver.getCompletedTime() - start)));
-                    removeReceiver(receiver);
-                    return;
-                }
-            } else {
-                assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE, scanMode);
+            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                writeOutput(String.format("discoverable() completed in %d ms",
+                        (receiver.getCompletedTime() - start)));
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -521,6 +610,12 @@
                 firedFlags, mask));
     }
 
+    /**
+     * Puts the local device into connectable only mode and checks to make sure that the local
+     * device is in in connectable mode and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     */
     public void undiscoverable(BluetoothAdapter adapter) {
         int mask = BluetoothReceiver.SCAN_MODE_CONNECTABLE_FLAG;
 
@@ -539,17 +634,14 @@
         long start = System.currentTimeMillis();
         assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
 
-        while (System.currentTimeMillis() - start < SET_SCAN_MODE_TIMEOUT) {
+        while (System.currentTimeMillis() - start < DISCOVERABLE_UNDISCOVERABLE_TIMEOUT) {
             scanMode = adapter.getScanMode();
-            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    writeOutput(String.format("undiscoverable() completed in %d ms",
-                            (receiver.getCompletedTime() - start)));
-                    removeReceiver(receiver);
-                    return;
-                }
-            } else {
-                assertEquals(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, scanMode);
+            if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                writeOutput(String.format("undiscoverable() completed in %d ms",
+                        (receiver.getCompletedTime() - start)));
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -561,6 +653,12 @@
                 mask));
     }
 
+    /**
+     * Starts a scan for remote devices and checks to make sure that the local device is scanning
+     * and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     */
     public void startScan(BluetoothAdapter adapter) {
         int mask = BluetoothReceiver.DISCOVERY_STARTED_FLAG;
 
@@ -577,7 +675,7 @@
         long start = System.currentTimeMillis();
         assertTrue(adapter.startDiscovery());
 
-        while (System.currentTimeMillis() - start < START_DISCOVERY_TIMEOUT) {
+        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
             if (adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
                 writeOutput(String.format("startScan() completed in %d ms",
                         (receiver.getCompletedTime() - start)));
@@ -593,6 +691,12 @@
                 adapter.isDiscovering(), firedFlags, mask));
     }
 
+    /**
+     * Stops a scan for remote devices and checks to make sure that the local device is not scanning
+     * and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     */
     public void stopScan(BluetoothAdapter adapter) {
         int mask = BluetoothReceiver.DISCOVERY_FINISHED_FLAG;
 
@@ -610,7 +714,7 @@
         // TODO: put assertTrue() around cancelDiscovery() once it starts returning true.
         adapter.cancelDiscovery();
 
-        while (System.currentTimeMillis() - start < CANCEL_DISCOVERY_TIMEOUT) {
+        while (System.currentTimeMillis() - start < START_STOP_SCAN_TIMEOUT) {
             if (!adapter.isDiscovering() && ((receiver.getFiredFlags() & mask) == mask)) {
                 writeOutput(String.format("stopScan() completed in %d ms",
                         (receiver.getCompletedTime() - start)));
@@ -627,20 +731,84 @@
 
     }
 
+    /**
+     * Enables PAN tethering on the local device and checks to make sure that tethering is enabled.
+     *
+     * @param adapter The BT adapter.
+     */
+    public void enablePan(BluetoothAdapter adapter) {
+        BluetoothPan pan = new BluetoothPan(mContext);
+        assertNotNull(pan);
+
+        long start = System.currentTimeMillis();
+        pan.setBluetoothTethering(true);
+        long stop = System.currentTimeMillis();
+        assertTrue(pan.isTetheringOn());
+
+        writeOutput(String.format("enablePan() completed in %d ms", (stop - start)));
+    }
+
+    /**
+     * Disables PAN tethering on the local device and checks to make sure that tethering is
+     * disabled.
+     *
+     * @param adapter The BT adapter.
+     */
+    public void disablePan(BluetoothAdapter adapter) {
+        BluetoothPan pan = new BluetoothPan(mContext);
+        assertNotNull(pan);
+
+        long start = System.currentTimeMillis();
+        pan.setBluetoothTethering(false);
+        long stop = System.currentTimeMillis();
+        assertFalse(pan.isTetheringOn());
+
+        writeOutput(String.format("disablePan() completed in %d ms", (stop - start)));
+    }
+
+    /**
+     * Initiates a pairing with a remote device and checks to make sure that the devices are paired
+     * and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
+     * @param pin The pairing pin if pairing requires a pin. Any value if not.
+     */
     public void pair(BluetoothAdapter adapter, BluetoothDevice device, int passkey, byte[] pin) {
         pairOrAcceptPair(adapter, device, passkey, pin, true);
     }
 
+    /**
+     * Accepts a pairing with a remote device and checks to make sure that the devices are paired
+     * and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
+     * @param pin The pairing pin if pairing requires a pin. Any value if not.
+     */
     public void acceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
             byte[] pin) {
         pairOrAcceptPair(adapter, device, passkey, pin, false);
     }
 
+    /**
+     * Helper method used by {@link #pair(BluetoothAdapter, BluetoothDevice, int, byte[])} and
+     * {@link #acceptPair(BluetoothAdapter, BluetoothDevice, int, byte[])} to either pair or accept
+     * a pairing request.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param passkey The pairing passkey if pairing requires a passkey. Any value if not.
+     * @param pin The pairing pin if pairing requires a pin. Any value if not.
+     * @param shouldPair Whether to pair or accept the pair.
+     */
     private void pairOrAcceptPair(BluetoothAdapter adapter, BluetoothDevice device, int passkey,
-            byte[] pin, boolean pair) {
+            byte[] pin, boolean shouldPair) {
         int mask = PairReceiver.STATE_BONDING_FLAG | PairReceiver.STATE_BONDED_FLAG;
         long start = -1;
-        String methodName = pair ? "pair()" : "acceptPair()";
+        String methodName = shouldPair ? "pair()" : "acceptPair()";
 
         if (!adapter.isEnabled()) {
             fail(methodName + " bluetooth not enabled");
@@ -653,7 +821,7 @@
             case BluetoothDevice.BOND_NONE:
                 assertFalse(adapter.getBondedDevices().contains(device));
                 start = System.currentTimeMillis();
-                if (pair) {
+                if (shouldPair) {
                     assertTrue(device.createBond());
                 }
                 break;
@@ -670,21 +838,19 @@
         }
 
         long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < PAIR_TIMEOUT) {
+        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
             state = device.getBondState();
-            if (state == BluetoothDevice.BOND_BONDED) {
+            if (state == BluetoothDevice.BOND_BONDED && (receiver.getFiredFlags() & mask) == mask) {
                 assertTrue(adapter.getBondedDevices().contains(device));
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    long finish = receiver.getCompletedTime();
-                    if (start != -1 && finish != -1) {
-                        writeOutput(String.format("%s completed in %d ms: device=%s", methodName,
-                                (finish - start), device));
-                    } else {
-                        writeOutput(String.format("%s completed: device=%s", methodName, device));
-                    }
-                    removeReceiver(receiver);
-                    return;
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("%s completed in %d ms: device=%s", methodName,
+                            (finish - start), device));
+                } else {
+                    writeOutput(String.format("%s completed: device=%s", methodName, device));
                 }
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -696,6 +862,13 @@
                 BluetoothDevice.BOND_BONDED, firedFlags, mask));
     }
 
+    /**
+     * Deletes a pairing with a remote device and checks to make sure that the devices are unpaired
+     * and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
     public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
         int mask = PairReceiver.STATE_NONE_FLAG;
         long start = -1;
@@ -727,20 +900,19 @@
         }
 
         long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < UNPAIR_TIMEOUT) {
-            if (device.getBondState() == BluetoothDevice.BOND_NONE) {
+        while (System.currentTimeMillis() - s < PAIR_UNPAIR_TIMEOUT) {
+            if (device.getBondState() == BluetoothDevice.BOND_NONE
+                    && (receiver.getFiredFlags() & mask) == mask) {
                 assertFalse(adapter.getBondedDevices().contains(device));
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    long finish = receiver.getCompletedTime();
-                    if (start != -1 && finish != -1) {
-                        writeOutput(String.format("unpair() completed in %d ms: device=%s",
-                                (finish - start), device));
-                    } else {
-                        writeOutput(String.format("unpair() completed: device=%s", device));
-                    }
-                    removeReceiver(receiver);
-                    return;
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("unpair() completed in %d ms: device=%s",
+                            (finish - start), device));
+                } else {
+                    writeOutput(String.format("unpair() completed: device=%s", device));
                 }
+                removeReceiver(receiver);
+                return;
             }
         }
 
@@ -751,6 +923,15 @@
                 firedFlags, mask));
     }
 
+    /**
+     * Connects a profile from the local device to a remote device and checks to make sure that the
+     * profile is connected and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP} or
+     *        {@link BluetoothProfile#HEADSET}.
+     */
     public void connectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile) {
         int mask = (ConnectProfileReceiver.STATE_CONNECTING_FLAG
                 | ConnectProfileReceiver.STATE_CONNECTED_FLAG);
@@ -794,21 +975,20 @@
         }
 
         long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < CONNECT_PROFILE_TIMEOUT) {
+        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
             state = proxy.getConnectionState(device);
-            if (state == BluetoothProfile.STATE_CONNECTED) {
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    long finish = receiver.getCompletedTime();
-                    if (start != -1 && finish != -1) {
-                        writeOutput(String.format("connectProfile() completed in %d ms: "
-                                + "device=%s, profile=%d", (finish - start), device, profile));
-                    } else {
-                        writeOutput(String.format("connectProfile() completed: device=%s, "
-                                + "profile=%d", device, profile));
-                    }
-                    removeReceiver(receiver);
-                    return;
+            if (state == BluetoothProfile.STATE_CONNECTED
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("connectProfile() completed in %d ms: "
+                            + "device=%s, profile=%d", (finish - start), device, profile));
+                } else {
+                    writeOutput(String.format("connectProfile() completed: device=%s, "
+                            + "profile=%d", device, profile));
                 }
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -820,6 +1000,15 @@
                 BluetoothProfile.STATE_CONNECTED, firedFlags, mask));
     }
 
+    /**
+     * Disconnects a profile between the local device and a remote device and checks to make sure
+     * that the profile is disconnected and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param profile The profile to disconnect. One of {@link BluetoothProfile#A2DP} or
+     *        {@link BluetoothProfile#HEADSET}.
+     */
     public void disconnectProfile(BluetoothAdapter adapter, BluetoothDevice device, int profile) {
         int mask = (ConnectProfileReceiver.STATE_DISCONNECTING_FLAG
                 | ConnectProfileReceiver.STATE_DISCONNECTED_FLAG);
@@ -863,21 +1052,20 @@
         }
 
         long s = System.currentTimeMillis();
-        while (System.currentTimeMillis() - s < DISCONNECT_PROFILE_TIMEOUT) {
+        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
             state = proxy.getConnectionState(device);
-            if (state == BluetoothProfile.STATE_DISCONNECTED) {
-                if ((receiver.getFiredFlags() & mask) == mask) {
-                    long finish = receiver.getCompletedTime();
-                    if (start != -1 && finish != -1) {
-                        writeOutput(String.format("disconnectProfile() completed in %d ms: "
-                                + "device=%s, profile=%d", (finish - start), device, profile));
-                    } else {
-                        writeOutput(String.format("disconnectProfile() completed: device=%s, "
-                                + "profile=%d", device, profile));
-                    }
-                    removeReceiver(receiver);
-                    return;
+            if (state == BluetoothProfile.STATE_DISCONNECTED
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("disconnectProfile() completed in %d ms: "
+                            + "device=%s, profile=%d", (finish - start), device, profile));
+                } else {
+                    writeOutput(String.format("disconnectProfile() completed: device=%s, "
+                            + "profile=%d", device, profile));
                 }
+                removeReceiver(receiver);
+                return;
             }
             sleep(POLL_TIME);
         }
@@ -889,6 +1077,360 @@
                 BluetoothProfile.STATE_DISCONNECTED, firedFlags, mask));
     }
 
+    /**
+     * Connects the local device with a remote HID device and checks to make sure that the profile
+     * is connected and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
+    public void connectInput(BluetoothAdapter adapter, BluetoothDevice device) {
+        int mask = (ConnectInputReceiver.STATE_CONNECTING_FLAG
+                | ConnectInputReceiver.STATE_CONNECTED_FLAG);
+        long start = -1;
+
+        if (!adapter.isEnabled()) {
+            fail(String.format("connectInput() bluetooth not enabled: device=%s", device));
+        }
+
+        if (!adapter.getBondedDevices().contains(device)) {
+            fail(String.format("connectInput() device not paired: device=%s", device));
+        }
+
+        BluetoothInputDevice inputDevice = new BluetoothInputDevice(mContext);
+        assertNotNull(inputDevice);
+        ConnectInputReceiver receiver = getConnectInputReceiver(device, mask);
+
+        int state = inputDevice.getInputDeviceState(device);
+        switch (state) {
+            case BluetoothInputDevice.STATE_CONNECTED:
+                removeReceiver(receiver);
+                return;
+            case BluetoothInputDevice.STATE_CONNECTING:
+                mask = 0; // Don't check for received intents since we might have missed them.
+                break;
+            case BluetoothInputDevice.STATE_DISCONNECTED:
+            case BluetoothInputDevice.STATE_DISCONNECTING:
+                start = System.currentTimeMillis();
+                assertTrue(inputDevice.connectInputDevice(device));
+                break;
+            default:
+                removeReceiver(receiver);
+                fail(String.format("connectInput() invalid state: device=%s, state=%d", device,
+                        state));
+        }
+
+        long s = System.currentTimeMillis();
+        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
+            state = inputDevice.getInputDeviceState(device);
+            if (state == BluetoothInputDevice.STATE_CONNECTED
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("connectInput() completed in %d ms: device=%s",
+                            (finish - start), device));
+                } else {
+                    writeOutput(String.format("connectInput() completed: device=%s", device));
+                }
+                removeReceiver(receiver);
+                return;
+            }
+            sleep(POLL_TIME);
+        }
+
+        int firedFlags = receiver.getFiredFlags();
+        removeReceiver(receiver);
+        fail(String.format("connectInput() timeout: device=%s, state=%d (expected %d), "
+                + "flags=0x%x (expected 0x%s)", device, state, BluetoothInputDevice.STATE_CONNECTED,
+                firedFlags, mask));
+    }
+
+    /**
+     * Disconnects the local device with a remote HID device and checks to make sure that the
+     * profile is connected and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
+    public void disconnectInput(BluetoothAdapter adapter, BluetoothDevice device) {
+        int mask = (ConnectInputReceiver.STATE_DISCONNECTING_FLAG
+                | ConnectInputReceiver.STATE_DISCONNECTED_FLAG);
+        long start = -1;
+
+        if (!adapter.isEnabled()) {
+            fail(String.format("disconnectInput() bluetooth not enabled: device=%s", device));
+        }
+
+        if (!adapter.getBondedDevices().contains(device)) {
+            fail(String.format("disconnectInput() device not paired: device=%s", device));
+        }
+
+        BluetoothInputDevice inputDevice = new BluetoothInputDevice(mContext);
+        assertNotNull(inputDevice);
+        ConnectInputReceiver receiver = getConnectInputReceiver(device, mask);
+
+        int state = inputDevice.getInputDeviceState(device);
+        switch (state) {
+            case BluetoothInputDevice.STATE_CONNECTED:
+            case BluetoothInputDevice.STATE_CONNECTING:
+                start = System.currentTimeMillis();
+                assertTrue(inputDevice.disconnectInputDevice(device));
+                break;
+            case BluetoothInputDevice.STATE_DISCONNECTED:
+                removeReceiver(receiver);
+                return;
+            case BluetoothInputDevice.STATE_DISCONNECTING:
+                mask = 0; // Don't check for received intents since we might have missed them.
+                break;
+            default:
+                removeReceiver(receiver);
+                fail(String.format("disconnectInput() invalid state: device=%s, state=%d", device,
+                        state));
+        }
+
+        long s = System.currentTimeMillis();
+        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
+            state = inputDevice.getInputDeviceState(device);
+            if (state == BluetoothInputDevice.STATE_DISCONNECTED
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("disconnectInput() completed in %d ms: device=%s",
+                            (finish - start), device));
+                } else {
+                    writeOutput(String.format("disconnectInput() completed: device=%s", device));
+                }
+                removeReceiver(receiver);
+                return;
+            }
+            sleep(POLL_TIME);
+        }
+
+        int firedFlags = receiver.getFiredFlags();
+        removeReceiver(receiver);
+        fail(String.format("disconnectInput() timeout: device=%s, state=%d (expected %d), "
+                + "flags=0x%x (expected 0x%s)", device, state,
+                BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
+    }
+
+    /**
+     * Connects the PANU to a remote NAP and checks to make sure that the PANU is connected and that
+     * the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
+    public void connectPan(BluetoothAdapter adapter, BluetoothDevice device) {
+        connectPanOrIncomingPanConnection(adapter, device, true);
+    }
+
+    /**
+     * Checks that a remote PANU connects to the local NAP correctly and that the correct actions
+     * were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
+    public void incomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device) {
+        connectPanOrIncomingPanConnection(adapter, device, false);
+    }
+
+    /**
+     * Helper method used by {@link #connectPan(BluetoothAdapter, BluetoothDevice)} and
+     * {@link #incomingPanConnection(BluetoothAdapter, BluetoothDevice)} to either connect to a
+     * remote NAP or verify that a remote device connected to the local NAP.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param connect If the method should initiate the connection (is PANU)
+     */
+    private void connectPanOrIncomingPanConnection(BluetoothAdapter adapter, BluetoothDevice device,
+            boolean connect) {
+        long start = -1;
+        int mask, role;
+        String methodName;
+
+        if (connect) {
+            methodName = "connectPan()";
+            mask = (ConnectPanReceiver.STATE_CONNECTED_FLAG |
+                    ConnectPanReceiver.STATE_CONNECTING_FLAG);
+            role = BluetoothPan.LOCAL_PANU_ROLE;
+        } else {
+            methodName = "incomingPanConnection()";
+            mask = ConnectPanReceiver.STATE_CONNECTED_FLAG;
+            role = BluetoothPan.LOCAL_NAP_ROLE;
+        }
+
+        if (!adapter.isEnabled()) {
+            fail(String.format("%s bluetooth not enabled: device=%s", methodName, device));
+        }
+
+        if (!adapter.getBondedDevices().contains(device)) {
+            fail(String.format("%s device not paired: device=%s", methodName, device));
+        }
+
+        BluetoothPan pan = new BluetoothPan(mContext);
+        assertNotNull(pan);
+        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
+
+        int state = pan.getPanDeviceState(device);
+        switch (state) {
+            case BluetoothPan.STATE_CONNECTED:
+                removeReceiver(receiver);
+                return;
+            case BluetoothPan.STATE_CONNECTING:
+                mask = 0; // Don't check for received intents since we might have missed them.
+                break;
+            case BluetoothPan.STATE_DISCONNECTED:
+            case BluetoothPan.STATE_DISCONNECTING:
+                start = System.currentTimeMillis();
+                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
+                    Log.i("BT", "connect to pan");
+                    assertTrue(pan.connect(device));
+                }
+                break;
+            default:
+                removeReceiver(receiver);
+                fail(String.format("%s invalid state: device=%s, state=%d", methodName, device,
+                        state));
+        }
+
+        long s = System.currentTimeMillis();
+        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
+            state = pan.getPanDeviceState(device);
+            if (state == BluetoothPan.STATE_CONNECTED
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("%s completed in %d ms: device=%s", methodName,
+                            (finish - start), device));
+                } else {
+                    writeOutput(String.format("%s completed: device=%s", methodName, device));
+                }
+                removeReceiver(receiver);
+                return;
+            }
+            sleep(POLL_TIME);
+        }
+
+        int firedFlags = receiver.getFiredFlags();
+        removeReceiver(receiver);
+        fail(String.format("%s timeout: device=%s, state=%d (expected %d), "
+                + "flags=0x%x (expected 0x%s)", methodName, device, state,
+                BluetoothPan.STATE_CONNECTED, firedFlags, mask));
+    }
+
+    /**
+     * Disconnects the PANU from a remote NAP and checks to make sure that the PANU is disconnected
+     * and that the correct actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
+    public void disconnectPan(BluetoothAdapter adapter, BluetoothDevice device) {
+        disconnectFromRemoteOrVerifyConnectNap(adapter, device, true);
+    }
+
+    /**
+     * Checks that a remote PANU disconnects from the local NAP correctly and that the correct
+     * actions were broadcast.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     */
+    public void incomingPanDisconnection(BluetoothAdapter adapter, BluetoothDevice device) {
+        disconnectFromRemoteOrVerifyConnectNap(adapter, device, false);
+    }
+
+    /**
+     * Helper method used by {@link #disconnectPan(BluetoothAdapter, BluetoothDevice)} and
+     * {@link #incomingPanDisconnection(BluetoothAdapter, BluetoothDevice)} to either disconnect
+     * from a remote NAP or verify that a remote device disconnected from the local NAP.
+     *
+     * @param adapter The BT adapter.
+     * @param device The remote device.
+     * @param disconnect Whether the method should connect or verify.
+     */
+    private void disconnectFromRemoteOrVerifyConnectNap(BluetoothAdapter adapter,
+            BluetoothDevice device, boolean disconnect) {
+        long start = -1;
+        int mask, role;
+        String methodName;
+
+        if (disconnect) {
+            methodName = "disconnectPan()";
+            mask = (ConnectPanReceiver.STATE_DISCONNECTED_FLAG |
+                    ConnectPanReceiver.STATE_DISCONNECTING_FLAG);
+            role = BluetoothPan.LOCAL_PANU_ROLE;
+        } else {
+            methodName = "incomingPanDisconnection()";
+            mask = ConnectPanReceiver.STATE_DISCONNECTED_FLAG;
+            role = BluetoothPan.LOCAL_NAP_ROLE;
+        }
+
+        if (!adapter.isEnabled()) {
+            fail(String.format("%s bluetooth not enabled: device=%s", methodName, device));
+        }
+
+        if (!adapter.getBondedDevices().contains(device)) {
+            fail(String.format("%s device not paired: device=%s", methodName, device));
+        }
+
+        BluetoothPan pan = new BluetoothPan(mContext);
+        assertNotNull(pan);
+        ConnectPanReceiver receiver = getConnectPanReceiver(device, role, mask);
+
+        int state = pan.getPanDeviceState(device);
+        switch (state) {
+            case BluetoothInputDevice.STATE_CONNECTED:
+            case BluetoothInputDevice.STATE_CONNECTING:
+                start = System.currentTimeMillis();
+                if (role == BluetoothPan.LOCAL_PANU_ROLE) {
+                    assertTrue(pan.disconnect(device));
+                }
+                break;
+            case BluetoothInputDevice.STATE_DISCONNECTED:
+                removeReceiver(receiver);
+                return;
+            case BluetoothInputDevice.STATE_DISCONNECTING:
+                mask = 0; // Don't check for received intents since we might have missed them.
+                break;
+            default:
+                removeReceiver(receiver);
+                fail(String.format("%s invalid state: device=%s, state=%d", methodName, device,
+                        state));
+        }
+
+        long s = System.currentTimeMillis();
+        while (System.currentTimeMillis() - s < CONNECT_DISCONNECT_PROFILE_TIMEOUT) {
+            state = pan.getPanDeviceState(device);
+            if (state == BluetoothInputDevice.STATE_DISCONNECTED
+                    && (receiver.getFiredFlags() & mask) == mask) {
+                long finish = receiver.getCompletedTime();
+                if (start != -1 && finish != -1) {
+                    writeOutput(String.format("%s completed in %d ms: device=%s", methodName,
+                            (finish - start), device));
+                } else {
+                    writeOutput(String.format("%s completed: device=%s", methodName, device));
+                }
+                removeReceiver(receiver);
+                return;
+            }
+            sleep(POLL_TIME);
+        }
+
+        int firedFlags = receiver.getFiredFlags();
+        removeReceiver(receiver);
+        fail(String.format("%s timeout: device=%s, state=%d (expected %d), "
+                + "flags=0x%x (expected 0x%s)", methodName, device, state,
+                BluetoothInputDevice.STATE_DISCONNECTED, firedFlags, mask));
+    }
+
+    /**
+     * Writes a string to the logcat and a file if a file has been specified in the constructor.
+     *
+     * @param s The string to be written.
+     */
     public void writeOutput(String s) {
         Log.i(mTag, s);
         if (mOutputWriter == null) {
@@ -902,38 +1444,60 @@
         }
     }
 
-    private BluetoothReceiver getBluetoothReceiver(int expectedFlags) {
-        BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags);
+    private void addReceiver(BroadcastReceiver receiver, String[] actions) {
         IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
-        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
-        filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
-        filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+        for (String action: actions) {
+            filter.addAction(action);
+        }
         mContext.registerReceiver(receiver, filter);
         mReceivers.add(receiver);
+    }
+
+    private BluetoothReceiver getBluetoothReceiver(int expectedFlags) {
+        String[] actions = {
+                BluetoothAdapter.ACTION_DISCOVERY_FINISHED,
+                BluetoothAdapter.ACTION_DISCOVERY_STARTED,
+                BluetoothAdapter.ACTION_SCAN_MODE_CHANGED,
+                BluetoothAdapter.ACTION_STATE_CHANGED};
+        BluetoothReceiver receiver = new BluetoothReceiver(expectedFlags);
+        addReceiver(receiver, actions);
         return receiver;
     }
 
     private PairReceiver getPairReceiver(BluetoothDevice device, int passkey, byte[] pin,
             int expectedFlags) {
+        String[] actions = {
+                BluetoothDevice.ACTION_PAIRING_REQUEST,
+                BluetoothDevice.ACTION_BOND_STATE_CHANGED};
         PairReceiver receiver = new PairReceiver(device, passkey, pin, expectedFlags);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
-        filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        mContext.registerReceiver(receiver, filter);
-        mReceivers.add(receiver);
+        addReceiver(receiver, actions);
         return receiver;
     }
 
     private ConnectProfileReceiver getConnectProfileReceiver(BluetoothDevice device, int profile,
             int expectedFlags) {
+        String[] actions = {
+                BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
+                BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED};
         ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
                 expectedFlags);
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
-        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
-        mContext.registerReceiver(receiver, filter);
-        mReceivers.add(receiver);
+        addReceiver(receiver, actions);
+        return receiver;
+    }
+
+    private ConnectInputReceiver getConnectInputReceiver(BluetoothDevice device,
+            int expectedFlags) {
+        String[] actions = {BluetoothInputDevice.ACTION_INPUT_DEVICE_STATE_CHANGED};
+        ConnectInputReceiver receiver = new ConnectInputReceiver(device, expectedFlags);
+        addReceiver(receiver, actions);
+        return receiver;
+    }
+
+    private ConnectPanReceiver getConnectPanReceiver(BluetoothDevice device, int role,
+            int expectedFlags) {
+        String[] actions = {BluetoothPan.ACTION_PAN_STATE_CHANGED};
+        ConnectPanReceiver receiver = new ConnectPanReceiver(device, role, expectedFlags);
+        addReceiver(receiver, actions);
         return receiver;
     }
 
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 1ee121e..0aefc31 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -262,6 +262,24 @@
 # key 240 "KEY_UNKNOWN"
 
 
+key 288   BUTTON_1
+key 289   BUTTON_2
+key 290   BUTTON_3
+key 291   BUTTON_4
+key 292   BUTTON_5
+key 293   BUTTON_6
+key 294   BUTTON_7
+key 295   BUTTON_8
+key 296   BUTTON_9
+key 297   BUTTON_10
+key 298   BUTTON_11
+key 299   BUTTON_12
+key 300   BUTTON_13
+key 301   BUTTON_14
+key 302   BUTTON_15
+key 303   BUTTON_16
+
+
 key 304   BUTTON_A
 key 305   BUTTON_B
 key 306   BUTTON_C
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index ca934ec..746597f 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -464,37 +464,28 @@
           <li><a href="<?cs var:toroot ?>guide/developing/tools/mksdcard.html">mksdcard</a></li>
 
           <li><a href="/guide/developing/tools/monkey.html">Monkey</a></li>
-              <li class="toggle-list">
-                 <div>
-                     <a href="/guide/developing/tools/monkeyrunner_concepts.html">
-                     <span class="en">monkeyrunner</span>
-                  </a>
-                  </div>
-                  <ul>
-                      <li>
-                          <a href="/guide/developing/tools/MonkeyDevice.html">
-                                <span class="en">MonkeyDevice</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a href="/guide/developing/tools/MonkeyImage.html">
-                            <span class="en">MonkeyImage</span>
-                        </a>
-                    </li>
-                    <li>
-                        <a href="/guide/developing/tools/MonkeyRunner.html">
-                            <span class="en">MonkeyRunner</span>
-                        </a>
-                    </li>
-                  </ul>
-              </li>
-              <li><a href="<?cs var:toroot ?>guide/developing/tools/proguard.html">ProGuard</a>
-<span class="new">new!</span></li>
-              <li><a href="<?cs var:toroot ?>guide/developing/tools/adb.html#sqlite">sqlite3</a></li>
-              <li><a href="<?cs var:toroot ?>guide/developing/tools/traceview.html" >Traceview</a></li>
-              <li><a href="<?cs var:toroot ?>guide/developing/tools/zipalign.html" >zipalign</a></li>
-          </ul>
-        </li>
+          <li class="toggle-list">
+            <div><a href="/guide/developing/tools/monkeyrunner_concepts.html">
+              <span class="en">monkeyrunner</span>
+            </a></div>
+            <ul>
+              <li><a href="/guide/developing/tools/MonkeyDevice.html">
+                <span class="en">MonkeyDevice</span>
+                </a></li>
+              <li><a href="/guide/developing/tools/MonkeyImage.html">
+                <span class="en">MonkeyImage</span>
+                </a></li>
+              <li><a href="/guide/developing/tools/MonkeyRunner.html">
+                <span class="en">MonkeyRunner</span>
+                </a></li>
+            </ul>
+          </li>
+          <li><a href="/guide/developing/tools/proguard.html">ProGuard</a></li>
+          <li><a href="/guide/developing/tools/adb.html#sqlite">sqlite3</a></li>
+          <li><a href="/guide/developing/tools/traceview.html">Traceview</a></li>
+          <li><a href="<?cs var:toroot ?>guide/developing/tools/zipalign.html">zipalign</a></li>
+        </ul>
+      </li>
     </ul>
   </li>
 
diff --git a/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp
index b6e0c30..ef7d274 100644
--- a/drm/drmserver/DrmManager.cpp
+++ b/drm/drmserver/DrmManager.cpp
@@ -87,7 +87,7 @@
 }
 
 status_t DrmManager::loadPlugIns() {
-    String8 pluginDirPath("/system/lib/drm/plugins/native");
+    String8 pluginDirPath("/system/lib/drm");
     return loadPlugIns(pluginDirPath);
 }
 
diff --git a/include/ui/KeycodeLabels.h b/include/ui/KeycodeLabels.h
index 9b1a897..dbccf29 100755
--- a/include/ui/KeycodeLabels.h
+++ b/include/ui/KeycodeLabels.h
@@ -212,6 +212,22 @@
     { "PROG_YELLOW", 185 },
     { "PROG_BLUE", 186 },
     { "APP_SWITCH", 187 },
+    { "BUTTON_1", 188 },
+    { "BUTTON_2", 189 },
+    { "BUTTON_3", 190 },
+    { "BUTTON_4", 191 },
+    { "BUTTON_5", 192 },
+    { "BUTTON_6", 193 },
+    { "BUTTON_7", 194 },
+    { "BUTTON_8", 195 },
+    { "BUTTON_9", 196 },
+    { "BUTTON_10", 197 },
+    { "BUTTON_11", 198 },
+    { "BUTTON_12", 199 },
+    { "BUTTON_13", 200 },
+    { "BUTTON_14", 201 },
+    { "BUTTON_15", 202 },
+    { "BUTTON_16", 203 },
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/libs/surfaceflinger_client/ISurfaceComposer.cpp b/libs/surfaceflinger_client/ISurfaceComposer.cpp
index 2216824..01ae23f 100644
--- a/libs/surfaceflinger_client/ISurfaceComposer.cpp
+++ b/libs/surfaceflinger_client/ISurfaceComposer.cpp
@@ -266,13 +266,13 @@
             int32_t mode = data.readInt32();
             status_t res = turnElectronBeamOff(mode);
             reply->writeInt32(res);
-        }
+        } break;
         case TURN_ELECTRON_BEAM_ON: {
             CHECK_INTERFACE(ISurfaceComposer, data, reply);
             int32_t mode = data.readInt32();
             status_t res = turnElectronBeamOn(mode);
             reply->writeInt32(res);
-        }
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/native/include/android/input.h b/native/include/android/input.h
index e196686..bad363d 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -333,6 +333,7 @@
     AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
     AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
     AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
+    AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
 };
 
 enum {
@@ -340,10 +341,12 @@
 
     AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
     AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
+    AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
     AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
     AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
     AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
     AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
+    AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
 
     AINPUT_SOURCE_ANY = 0xffffff00,
 };
diff --git a/native/include/android/keycodes.h b/native/include/android/keycodes.h
index b026a0c..c4a7eff 100644
--- a/native/include/android/keycodes.h
+++ b/native/include/android/keycodes.h
@@ -231,6 +231,22 @@
     AKEYCODE_PROG_YELLOW     = 185,
     AKEYCODE_PROG_BLUE       = 186,
     AKEYCODE_APP_SWITCH      = 187,
+    AKEYCODE_BUTTON_1        = 188,
+    AKEYCODE_BUTTON_2        = 189,
+    AKEYCODE_BUTTON_3        = 190,
+    AKEYCODE_BUTTON_4        = 191,
+    AKEYCODE_BUTTON_5        = 192,
+    AKEYCODE_BUTTON_6        = 193,
+    AKEYCODE_BUTTON_7        = 194,
+    AKEYCODE_BUTTON_8        = 195,
+    AKEYCODE_BUTTON_9        = 196,
+    AKEYCODE_BUTTON_10       = 197,
+    AKEYCODE_BUTTON_11       = 198,
+    AKEYCODE_BUTTON_12       = 199,
+    AKEYCODE_BUTTON_13       = 200,
+    AKEYCODE_BUTTON_14       = 201,
+    AKEYCODE_BUTTON_15       = 202,
+    AKEYCODE_BUTTON_16       = 203,
 
     // NOTE: If you add a new keycode here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
diff --git a/packages/VpnServices/src/com/android/server/vpn/VpnService.java b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
index 63b87b1..a618423 100644
--- a/packages/VpnServices/src/com/android/server/vpn/VpnService.java
+++ b/packages/VpnServices/src/com/android/server/vpn/VpnService.java
@@ -328,6 +328,7 @@
             public void run() {
                 Log.i(TAG, "VPN connectivity monitor running");
                 try {
+                    mNotification.update(mStartTime); // to pop up notification
                     for (int i = 10; ; i--) {
                         long now = System.currentTimeMillis();
 
@@ -417,13 +418,27 @@
 
     // Helper class for showing, updating notification.
     private class NotificationHelper {
+        private NotificationManager mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        private Notification mNotification =
+                new Notification(R.drawable.vpn_connected, null, 0L);
+        private PendingIntent mPendingIntent = PendingIntent.getActivity(
+                mContext, 0,
+                new VpnManager(mContext).createSettingsActivityIntent(), 0);
+        private String mConnectedTitle;
+
         void update(long now) {
-            String title = getNotificationTitle(true);
-            Notification n = new Notification(R.drawable.vpn_connected, title,
-                    mStartTime);
-            n.setLatestEventInfo(mContext, title,
+            Notification n = mNotification;
+            if (now == mStartTime) {
+                // to pop up the notification for the first time
+                n.when = mStartTime;
+                n.tickerText = mConnectedTitle = getNotificationTitle(true);
+            } else {
+                n.tickerText = null;
+            }
+            n.setLatestEventInfo(mContext, mConnectedTitle,
                     getConnectedNotificationMessage(now),
-                    prepareNotificationIntent());
+                    mPendingIntent);
             n.flags |= Notification.FLAG_NO_CLEAR;
             n.flags |= Notification.FLAG_ONGOING_EVENT;
             enableNotification(n);
@@ -435,25 +450,18 @@
                     title, System.currentTimeMillis());
             n.setLatestEventInfo(mContext, title,
                     getDisconnectedNotificationMessage(),
-                    prepareNotificationIntent());
+                    mPendingIntent);
             n.flags |= Notification.FLAG_AUTO_CANCEL;
             disableNotification();
             enableNotification(n);
         }
 
         void disableNotification() {
-            ((NotificationManager) mContext.getSystemService(
-                    Context.NOTIFICATION_SERVICE)).cancel(NOTIFICATION_ID);
+            mNotificationManager.cancel(NOTIFICATION_ID);
         }
 
         private void enableNotification(Notification n) {
-            ((NotificationManager) mContext.getSystemService(
-                    Context.NOTIFICATION_SERVICE)).notify(NOTIFICATION_ID, n);
-        }
-
-        private PendingIntent prepareNotificationIntent() {
-            return PendingIntent.getActivity(mContext, 0,
-                    new VpnManager(mContext).createSettingsActivityIntent(), 0);
+            mNotificationManager.notify(NOTIFICATION_ID, n);
         }
 
         private String getNotificationTitle(boolean connected) {
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index 5e33f05..1f06dcc 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -221,7 +221,8 @@
         final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
 
         ab.setAdapter(mAdapter, this)
-                .setInverseBackgroundForced(true);
+                .setInverseBackgroundForced(true)
+                .setTitle(R.string.global_actions);
 
         final AlertDialog dialog = ab.create();
         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
@@ -248,7 +249,6 @@
         } else {
             mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
         }
-        mDialog.setTitle(R.string.global_actions);
     }
 
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 68c1453..b9e93fd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1279,6 +1279,11 @@
         return mDecor.superDispatchTrackballEvent(event);
     }
 
+    @Override
+    public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+        return mDecor.superDispatchGenericMotionEvent(event);
+    }
+
     /**
      * A key was pressed down and not handled by anything else in the window.
      *
@@ -1692,6 +1697,13 @@
                     .dispatchTrackballEvent(ev);
         }
 
+        @Override
+        public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+            final Callback cb = getCallback();
+            return cb != null && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev) : super
+                    .dispatchGenericMotionEvent(ev);
+        }
+
         public boolean superDispatchKeyEvent(KeyEvent event) {
             return super.dispatchKeyEvent(event);
         }
@@ -1708,6 +1720,10 @@
             return super.dispatchTrackballEvent(event);
         }
 
+        public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+            return super.dispatchGenericMotionEvent(event);
+        }
+
         @Override
         public boolean onTouchEvent(MotionEvent event) {
             return onInterceptTouchEvent(event);
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 487e73f..efdb177 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -624,7 +624,11 @@
         AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1,
         AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2,
         AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR,
-        AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE
+        AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE,
+        AKEYCODE_BUTTON_1, AKEYCODE_BUTTON_2, AKEYCODE_BUTTON_3, AKEYCODE_BUTTON_4,
+        AKEYCODE_BUTTON_5, AKEYCODE_BUTTON_6, AKEYCODE_BUTTON_7, AKEYCODE_BUTTON_8,
+        AKEYCODE_BUTTON_9, AKEYCODE_BUTTON_10, AKEYCODE_BUTTON_11, AKEYCODE_BUTTON_12,
+        AKEYCODE_BUTTON_13, AKEYCODE_BUTTON_14, AKEYCODE_BUTTON_15, AKEYCODE_BUTTON_16,
 };
 
 int EventHub::openDevice(const char *devicePath) {
@@ -739,9 +743,9 @@
         //}
 
         // See if this is a keyboard.  Ignore everything in the button range except for
-        // gamepads which are also considered keyboards.
+        // joystick and gamepad buttons which are also considered keyboards.
         if (containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC))
-                || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_GAMEPAD),
+                || containsNonZeroByte(key_bitmask, sizeof_bit_array(BTN_JOYSTICK),
                         sizeof_bit_array(BTN_DIGI))
                 || containsNonZeroByte(key_bitmask, sizeof_bit_array(KEY_OK),
                         sizeof_bit_array(KEY_MAX + 1))) {
@@ -856,6 +860,18 @@
         }
     }
 
+    // See if this device is a joystick.
+    // Ignore touchscreens because they use the same absolute axes for other purposes.
+    if (device->classes & INPUT_DEVICE_CLASS_GAMEPAD
+            && !(device->classes & INPUT_DEVICE_CLASS_TOUCHSCREEN)) {
+        if (test_bit(ABS_X, abs_bitmask)
+                || test_bit(ABS_Y, abs_bitmask)
+                || test_bit(ABS_HAT0X, abs_bitmask)
+                || test_bit(ABS_HAT0Y, abs_bitmask)) {
+            device->classes |= INPUT_DEVICE_CLASS_JOYSTICK;
+        }
+    }
+
     // If the device isn't recognized as something we handle, don't monitor it.
     if (device->classes == 0) {
         LOGV("Dropping device: id=%d, path='%s', name='%s'",
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index 74b7ec5..3b5a1ea 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -85,7 +85,7 @@
     int32_t flat;      // center flat position, eg. flat == 8 means center is between -8 and 8
     int32_t fuzz;      // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
 
-    inline int32_t getRange() { return maxValue - minValue; }
+    inline int32_t getRange() const { return maxValue - minValue; }
 
     inline void clear() {
         valid = false;
@@ -100,7 +100,7 @@
  * Input device classes.
  */
 enum {
-    /* The input device is a keyboard. */
+    /* The input device is a keyboard or has buttons. */
     INPUT_DEVICE_CLASS_KEYBOARD      = 0x00000001,
 
     /* The input device is an alpha-numeric keyboard (not just a dial pad). */
@@ -123,6 +123,9 @@
 
     /* The input device has switches. */
     INPUT_DEVICE_CLASS_SWITCH        = 0x00000080,
+
+    /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
+    INPUT_DEVICE_CLASS_JOYSTICK      = 0x00000100,
 };
 
 /*
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6b66791..77b745f 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -250,6 +250,9 @@
     if (classes & INPUT_DEVICE_CLASS_DPAD) {
         keyboardSources |= AINPUT_SOURCE_DPAD;
     }
+    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+        keyboardSources |= AINPUT_SOURCE_GAMEPAD;
+    }
 
     if (keyboardSources != 0) {
         device->addMapper(new KeyboardInputMapper(device, keyboardSources, keyboardType));
@@ -267,6 +270,11 @@
         device->addMapper(new SingleTouchInputMapper(device));
     }
 
+    // Joystick-like devices.
+    if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
+        device->addMapper(new JoystickInputMapper(device));
+    }
+
     return device;
 }
 
@@ -715,6 +723,16 @@
     return 0;
 }
 
+void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
+        const RawAbsoluteAxisInfo& axis, const char* name) {
+    if (axis.valid) {
+        dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
+                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+    } else {
+        dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+    }
+}
+
 
 // --- SwitchInputMapper ---
 
@@ -857,7 +875,7 @@
 bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
     return scanCode < BTN_MOUSE
         || scanCode >= KEY_OK
-        || (scanCode >= BTN_GAMEPAD && scanCode < BTN_DIGI);
+        || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
 }
 
 void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
@@ -1486,25 +1504,16 @@
     mRawAxes.orientation.clear();
 }
 
-static void dumpAxisInfo(String8& dump, RawAbsoluteAxisInfo axis, const char* name) {
-    if (axis.valid) {
-        dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d\n",
-                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
-    } else {
-        dump.appendFormat(INDENT4 "%s: unknown range\n", name);
-    }
-}
-
 void TouchInputMapper::dumpRawAxes(String8& dump) {
     dump.append(INDENT3 "Raw Axes:\n");
-    dumpAxisInfo(dump, mRawAxes.x, "X");
-    dumpAxisInfo(dump, mRawAxes.y, "Y");
-    dumpAxisInfo(dump, mRawAxes.pressure, "Pressure");
-    dumpAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
-    dumpAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
-    dumpAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
-    dumpAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
-    dumpAxisInfo(dump, mRawAxes.orientation, "Orientation");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.x, "X");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.y, "Y");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.pressure, "Pressure");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.touchMajor, "TouchMajor");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.touchMinor, "TouchMinor");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.toolMajor, "ToolMajor");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.toolMinor, "ToolMinor");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.orientation, "Orientation");
 }
 
 bool TouchInputMapper::configureSurfaceLocked() {
@@ -3646,4 +3655,199 @@
 }
 
 
+// --- JoystickInputMapper ---
+
+JoystickInputMapper::JoystickInputMapper(InputDevice* device) :
+        InputMapper(device) {
+    initialize();
+}
+
+JoystickInputMapper::~JoystickInputMapper() {
+}
+
+uint32_t JoystickInputMapper::getSources() {
+    return AINPUT_SOURCE_JOYSTICK;
+}
+
+void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+    InputMapper::populateDeviceInfo(info);
+
+    if (mAxes.x.valid) {
+        info->addMotionRange(AINPUT_MOTION_RANGE_X,
+                mAxes.x.min, mAxes.x.max, mAxes.x.flat, mAxes.x.fuzz);
+    }
+    if (mAxes.y.valid) {
+        info->addMotionRange(AINPUT_MOTION_RANGE_Y,
+                mAxes.y.min, mAxes.y.max, mAxes.y.flat, mAxes.y.fuzz);
+    }
+}
+
+void JoystickInputMapper::dump(String8& dump) {
+    dump.append(INDENT2 "Joystick Input Mapper:\n");
+
+    dump.append(INDENT3 "Raw Axes:\n");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.x, "X");
+    dumpRawAbsoluteAxisInfo(dump, mRawAxes.y, "Y");
+
+    dump.append(INDENT3 "Normalized Axes:\n");
+    dumpNormalizedAxis(dump, mAxes.x, "X");
+    dumpNormalizedAxis(dump, mAxes.y, "Y");
+    dumpNormalizedAxis(dump, mAxes.hat0X, "Hat0X");
+    dumpNormalizedAxis(dump, mAxes.hat0Y, "Hat0Y");
+}
+
+void JoystickInputMapper::dumpNormalizedAxis(String8& dump,
+        const NormalizedAxis& axis, const char* name) {
+    if (axis.valid) {
+        dump.appendFormat(INDENT4 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, "
+                "scale=%0.3f, center=%0.3f, precision=%0.3f, value=%0.3f\n",
+                name, axis.min, axis.max, axis.flat, axis.fuzz,
+                axis.scale, axis.center, axis.precision, axis.value);
+    } else {
+        dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+    }
+}
+
+void JoystickInputMapper::configure() {
+    InputMapper::configure();
+
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mRawAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mRawAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_HAT0X, & mRawAxes.hat0X);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_HAT0Y, & mRawAxes.hat0Y);
+
+    mAxes.x.configure(mRawAxes.x);
+    mAxes.y.configure(mRawAxes.y);
+    mAxes.hat0X.configure(mRawAxes.hat0X);
+    mAxes.hat0Y.configure(mRawAxes.hat0Y);
+}
+
+void JoystickInputMapper::initialize() {
+    mAccumulator.clear();
+
+    mAxes.x.resetState();
+    mAxes.y.resetState();
+    mAxes.hat0X.resetState();
+    mAxes.hat0Y.resetState();
+}
+
+void JoystickInputMapper::reset() {
+    // Recenter all axes.
+    nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+    mAccumulator.clear();
+    mAccumulator.fields = Accumulator::FIELD_ALL;
+    sync(when);
+
+    // Reinitialize state.
+    initialize();
+
+    InputMapper::reset();
+}
+
+void JoystickInputMapper::process(const RawEvent* rawEvent) {
+    switch (rawEvent->type) {
+    case EV_ABS:
+        switch (rawEvent->scanCode) {
+        case ABS_X:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_X;
+            mAccumulator.absX = rawEvent->value;
+            break;
+        case ABS_Y:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_Y;
+            mAccumulator.absY = rawEvent->value;
+            break;
+        case ABS_HAT0X:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_HAT0X;
+            mAccumulator.absHat0X = rawEvent->value;
+            break;
+        case ABS_HAT0Y:
+            mAccumulator.fields |= Accumulator::FIELD_ABS_HAT0Y;
+            mAccumulator.absHat0Y = rawEvent->value;
+            break;
+        }
+        break;
+
+    case EV_SYN:
+        switch (rawEvent->scanCode) {
+        case SYN_REPORT:
+            sync(rawEvent->when);
+            break;
+        }
+        break;
+    }
+}
+
+void JoystickInputMapper::sync(nsecs_t when) {
+    uint32_t fields = mAccumulator.fields;
+    if (fields == 0) {
+        return; // no new state changes, so nothing to do
+    }
+
+    int32_t metaState = mContext->getGlobalMetaState();
+
+    bool motionAxisChanged = false;
+    if (fields & Accumulator::FIELD_ABS_X) {
+        if (mAxes.x.updateValue(mAccumulator.absX)) {
+            motionAxisChanged = true;
+        }
+    }
+
+    if (fields & Accumulator::FIELD_ABS_Y) {
+        if (mAxes.y.updateValue(mAccumulator.absY)) {
+            motionAxisChanged = true;
+        }
+    }
+
+    if (motionAxisChanged) {
+        PointerCoords pointerCoords;
+        pointerCoords.x = mAxes.x.value;
+        pointerCoords.y = mAxes.y.value;
+        pointerCoords.touchMajor = 0;
+        pointerCoords.touchMinor = 0;
+        pointerCoords.toolMajor = 0;
+        pointerCoords.toolMinor = 0;
+        pointerCoords.pressure = 0;
+        pointerCoords.size = 0;
+        pointerCoords.orientation = 0;
+
+        int32_t pointerId = 0;
+        getDispatcher()->notifyMotion(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, 0,
+                AMOTION_EVENT_ACTION_MOVE, 0, metaState, AMOTION_EVENT_EDGE_FLAG_NONE,
+                1, &pointerId, &pointerCoords, mAxes.x.precision, mAxes.y.precision, 0);
+    }
+
+    if (fields & Accumulator::FIELD_ABS_HAT0X) {
+        if (mAxes.hat0X.updateValueAndDirection(mAccumulator.absHat0X)) {
+            notifyDirectionalAxis(mAxes.hat0X, when, metaState,
+                    AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT);
+        }
+    }
+
+    if (fields & Accumulator::FIELD_ABS_HAT0Y) {
+        if (mAxes.hat0Y.updateValueAndDirection(mAccumulator.absHat0Y)) {
+            notifyDirectionalAxis(mAxes.hat0Y, when, metaState,
+                    AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN);
+        }
+    }
+
+    mAccumulator.clear();
+}
+
+void JoystickInputMapper::notifyDirectionalAxis(DirectionalAxis& axis,
+        nsecs_t when, int32_t metaState, int32_t lowKeyCode, int32_t highKeyCode) {
+    if (axis.lastKeyCode) {
+        getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, 0,
+                AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_FROM_SYSTEM,
+                axis.lastKeyCode, 0, metaState, when);
+        axis.lastKeyCode = 0;
+    }
+    if (axis.direction) {
+        axis.lastKeyCode = axis.direction > 0 ? highKeyCode : lowKeyCode;
+        getDispatcher()->notifyKey(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, 0,
+                AKEY_EVENT_ACTION_DOWN, AKEY_EVENT_FLAG_FROM_SYSTEM,
+                axis.lastKeyCode, 0, metaState, when);
+    }
+}
+
+
 } // namespace android
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 8b2d40a..613ed18 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -339,6 +339,9 @@
 protected:
     InputDevice* mDevice;
     InputReaderContext* mContext;
+
+    static void dumpRawAbsoluteAxisInfo(String8& dump,
+            const RawAbsoluteAxisInfo& axis, const char* name);
 };
 
 
@@ -951,6 +954,139 @@
     void sync(nsecs_t when);
 };
 
+
+class JoystickInputMapper : public InputMapper {
+public:
+    JoystickInputMapper(InputDevice* device);
+    virtual ~JoystickInputMapper();
+
+    virtual uint32_t getSources();
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+    virtual void dump(String8& dump);
+    virtual void configure();
+    virtual void reset();
+    virtual void process(const RawEvent* rawEvent);
+
+private:
+    struct RawAxes {
+        RawAbsoluteAxisInfo x;
+        RawAbsoluteAxisInfo y;
+        RawAbsoluteAxisInfo hat0X;
+        RawAbsoluteAxisInfo hat0Y;
+    } mRawAxes;
+
+    struct NormalizedAxis {
+        bool valid;
+
+        static const float min = -1.0f;
+        static const float max = -1.0f;
+
+        float scale;      // scale factor
+        float center;     // center offset after scaling
+        float precision;  // precision
+        float flat;       // size of flat region
+        float fuzz;       // error tolerance
+
+        float value;      // most recent value
+
+        NormalizedAxis() : valid(false), scale(0), center(0), precision(0),
+                flat(0), fuzz(0), value(0) {
+        }
+
+        void configure(const RawAbsoluteAxisInfo& rawAxis) {
+            if (rawAxis.valid && rawAxis.getRange() != 0) {
+                valid = true;
+                scale = 2.0f / rawAxis.getRange();
+                precision = rawAxis.getRange();
+                flat = rawAxis.flat * scale;
+                fuzz = rawAxis.fuzz * scale;
+                center = float(rawAxis.minValue + rawAxis.maxValue) / rawAxis.getRange();
+            }
+        }
+
+        void resetState() {
+            value = 0;
+        }
+
+        bool updateValue(int32_t rawValue) {
+            float newValue = rawValue * scale - center;
+            if (value == newValue) {
+                return false;
+            }
+            value = newValue;
+            return true;
+        }
+    };
+
+    struct DirectionalAxis : NormalizedAxis {
+        int32_t direction; // most recent direction vector: value is one of -1, 0, 1.
+
+        int32_t lastKeyCode;  // most recent key code produced
+
+        DirectionalAxis() : lastKeyCode(0) {
+        }
+
+        void resetState() {
+            NormalizedAxis::resetState();
+            direction = 0;
+            lastKeyCode = 0;
+        }
+
+        bool updateValueAndDirection(int32_t rawValue) {
+            if (!updateValue(rawValue)) {
+                return false;
+            }
+            if (value > flat) {
+                direction = 1;
+            } else if (value < -flat) {
+                direction = -1;
+            } else {
+                direction = 0;
+            }
+            return true;
+        }
+    };
+
+    struct Axes {
+        NormalizedAxis x;
+        NormalizedAxis y;
+        DirectionalAxis hat0X;
+        DirectionalAxis hat0Y;
+    } mAxes;
+
+    struct Accumulator {
+        enum {
+            FIELD_ABS_X = 1,
+            FIELD_ABS_Y = 2,
+            FIELD_ABS_HAT0X = 4,
+            FIELD_ABS_HAT0Y = 8,
+
+            FIELD_ALL = FIELD_ABS_X | FIELD_ABS_Y | FIELD_ABS_HAT0X | FIELD_ABS_HAT0Y,
+        };
+
+        uint32_t fields;
+
+        int32_t absX;
+        int32_t absY;
+        int32_t absHat0X;
+        int32_t absHat0Y;
+
+        inline void clear() {
+            fields = 0;
+        }
+    } mAccumulator;
+
+    void initialize();
+
+    void sync(nsecs_t when);
+
+    void notifyDirectionalAxis(DirectionalAxis& axis,
+            nsecs_t when, int32_t metaState, int32_t lowKeyCode, int32_t highKeyCode);
+
+    static void dumpNormalizedAxis(String8& dump,
+            const NormalizedAxis& axis, const char* name);
+};
+
 } // namespace android
 
 #endif // _UI_INPUT_READER_H
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 2321e30..d0df6cf 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -2129,7 +2129,8 @@
         if (proxy == null) proxy = new ProxyProperties("", 0, "");
         log("sending Proxy Broadcast for " + proxy);
         Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
-        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+            Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
         intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
         mContext.sendStickyBroadcast(intent);
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
old mode 100755
new mode 100644
index dbf9a96..3b1b8c4
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -9577,7 +9577,7 @@
                 // r.record is null if findServiceLocked() failed the caller permission check
                 if (r.record == null) {
                     throw new SecurityException(
-                            "Permission Denial: Accessing service "
+                            "Permission Denial: Accessing service " + r.record.name
                             + " from pid=" + Binder.getCallingPid()
                             + ", uid=" + Binder.getCallingUid()
                             + " requires " + r.permission);
diff --git a/telephony/java/com/android/internal/telephony/DataCallState.java b/telephony/java/com/android/internal/telephony/DataCallState.java
index d0f3d24..df12153 100644
--- a/telephony/java/com/android/internal/telephony/DataCallState.java
+++ b/telephony/java/com/android/internal/telephony/DataCallState.java
@@ -17,16 +17,43 @@
 
 package com.android.internal.telephony;
 
+/**
+ * This is RIL_Data_Call_Response_v5 from ril.h
+ * TODO: Rename to DataCallResponse.
+ */
 public class DataCallState {
-    public int cid;
-    public int active;
-    public String type;
-    public String apn;
-    public String address;
+    public int version = 0;
+    public int status = 0;
+    public int cid = 0;
+    public int active = 0;
+    public String type = "";
+    public String ifname = "";
+    public String [] addresses = new String[0];
+    public String [] dnses = new String[0];
 
     @Override
     public String toString() {
-        return "DataCallState: {" + " cid: " + cid + ", active: " + active + ", type: " + type
-                + ", apn: " + apn + ", address: " + address + " }";
+        StringBuffer sb = new StringBuffer();
+        sb.append("DataCallState: {")
+           .append("version=").append(version)
+           .append(" status=").append(status)
+           .append(" cid=").append(cid)
+           .append(" active=").append(active)
+           .append(" type=").append(type)
+           .append("' ifname='").append(ifname);
+        sb.append("' addresses=[");
+        for (String addr : addresses) {
+            sb.append(addr);
+            sb.append(",");
+        }
+        if (addresses.length > 0) sb.deleteCharAt(sb.length()-1);
+        sb.append("] dnses=[");
+        for (String addr : dnses) {
+            sb.append(addr);
+            sb.append(",");
+        }
+        if (dnses.length > 0) sb.deleteCharAt(sb.length()-1);
+        sb.append("]}");
+        return sb.toString();
     }
 }
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index c143424..31c89d0 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -27,13 +27,11 @@
 import android.os.Message;
 import android.os.SystemProperties;
 import android.text.TextUtils;
-import android.util.EventLog;
 
 import java.net.InetAddress;
-import java.net.InterfaceAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
+import java.net.Inet4Address;
 import java.net.UnknownHostException;
+import java.util.HashMap;
 
 /**
  * {@hide}
@@ -55,48 +53,6 @@
  * <code>AsyncResult.exception = new Exception()</code>.
  *
  * The other public methods are provided for debugging.
- *
- * Below is the state machine description for this class.
- *
- * DataConnection {
- *   + mDefaultState {
- *        EVENT_RESET { clearSettings, notifiyDisconnectCompleted, >mInactiveState }.
- *        EVENT_CONNECT {  notifyConnectCompleted(FailCause.UNKNOWN) }.
- *        EVENT_DISCONNECT { notifyDisconnectCompleted }.
- *
- *        // Ignored messages
- *        EVENT_SETUP_DATA_CONNECTION_DONE,
- *        EVENT_GET_LAST_FAIL_DONE,
- *        EVENT_DEACTIVATE_DONE.
- *     }
- *   ++ # mInactiveState
- *        e(doNotifications)
- *        x(clearNotifications) {
- *            EVENT_RESET { notifiyDisconnectCompleted }.
- *            EVENT_CONNECT {startConnecting, >mActivatingState }.
- *        }
- *   ++   mActivatingState {
- *            EVENT_DISCONNECT { %EVENT_DISCONNECT }.
- *            EVENT_SETUP_DATA_CONNECTION_DONE {
- *                  if (SUCCESS) { notifyConnectCompleted(FailCause.NONE), >mActiveState }.
- *                  if (ERR_BadCommand) {
- *                         notifyConnectCompleted(FailCause.UNKNOWN), >mInactiveState }.
- *                  if (ERR_BadDns) { tearDownData($DEACTIVATE_DONE), >mDisconnectingBadDnsState }.
- *                  if (ERR_Other) { getLastDataCallFailCause($EVENT_GET_LAST_FAIL_DONE) }.
- *                  if (ERR_Stale) {}.
- *            }
- *            EVENT_GET_LAST_FAIL_DONE { notifyConnectCompleted(result), >mInactive }.
- *        }
- *   ++   mActiveState {
- *            EVENT_DISCONNECT { tearDownData($EVENT_DEACTIVATE_DONE), >mDisconnecting }.
- *        }
- *   ++   mDisconnectingState {
- *            EVENT_DEACTIVATE_DONE { notifyDisconnectCompleted, >mInactiveState }.
- *        }
- *   ++   mDisconnectingBadDnsState {
- *            EVENT_DEACTIVATE_DONE { notifyConnectComplete(FailCause.UNKNOWN), >mInactiveState }.
- *        }
- *  }
  */
 public abstract class DataConnection extends HierarchicalStateMachine {
     protected static final boolean DBG = true;
@@ -108,24 +64,22 @@
      * Class returned by onSetupConnectionCompleted.
      */
     protected enum SetupResult {
+        SUCCESS,
         ERR_BadCommand,
-        ERR_BadDns,
-        ERR_Other,
+        ERR_UnacceptableParameter,
+        ERR_GetLastErrorFromRil,
         ERR_Stale,
-        SUCCESS;
+        ERR_RilError;
 
         public FailCause mFailCause;
 
+        SetupResult() {
+            mFailCause = FailCause.fromInt(0);
+        }
+
         @Override
         public String toString() {
-            switch (this) {
-                case ERR_BadCommand: return "Bad Command";
-                case ERR_BadDns: return "Bad DNS";
-                case ERR_Other: return "Other error";
-                case ERR_Stale: return "Stale command";
-                case SUCCESS: return "SUCCESS";
-                default: return "unknown";
-            }
+            return name() + "  SetupResult.mFailCause=" + mFailCause;
         }
     }
 
@@ -167,31 +121,66 @@
     }
 
     /**
-     * Returned as the reason for a connection failure.
+     * Returned as the reason for a connection failure as defined
+     * by RIL_DataCallFailCause in ril.h and some local errors.
      */
     public enum FailCause {
-        NONE,
-        OPERATOR_BARRED,
-        INSUFFICIENT_RESOURCES,
-        MISSING_UNKNOWN_APN,
-        UNKNOWN_PDP_ADDRESS,
-        USER_AUTHENTICATION,
-        ACTIVATION_REJECT_GGSN,
-        ACTIVATION_REJECT_UNSPECIFIED,
-        SERVICE_OPTION_NOT_SUPPORTED,
-        SERVICE_OPTION_NOT_SUBSCRIBED,
-        SERVICE_OPTION_OUT_OF_ORDER,
-        NSAPI_IN_USE,
-        PROTOCOL_ERRORS,
-        REGISTRATION_FAIL,
-        GPRS_REGISTRATION_FAIL,
-        UNKNOWN,
+        NONE(0),
 
-        RADIO_NOT_AVAILABLE;
+        // This series of errors as specified by the standards
+        // specified in ril.h
+        OPERATOR_BARRED(0x08),
+        INSUFFICIENT_RESOURCES(0x1A),
+        MISSING_UNKNOWN_APN(0x1B),
+        UNKNOWN_PDP_ADDRESS_TYPE(0x1C),
+        USER_AUTHENTICATION(0x1D),
+        ACTIVATION_REJECT_GGSN(0x1E),
+        ACTIVATION_REJECT_UNSPECIFIED(0x1F),
+        SERVICE_OPTION_NOT_SUPPORTED(0x20),
+        SERVICE_OPTION_NOT_SUBSCRIBED(0x21),
+        SERVICE_OPTION_OUT_OF_ORDER(0x22),
+        NSAPI_IN_USE(0x23),
+        ONLY_IPV4_ALLOWED(0x32),
+        ONLY_IPV6_ALLOWED(0x33),
+        ONLY_SINGLE_BEARER_ALLOWED(0x34),
+        PROTOCOL_ERRORS(0x6F),
+
+        // Local errors generated by Vendor RIL
+        // specified in ril.h
+        REGISTRATION_FAIL(-1),
+        GPRS_REGISTRATION_FAIL(-2),
+        SIGNAL_LOST(-3),
+        PREF_RADIO_TECH_CHANGED(-4),
+        RADIO_POWER_OFF(-5),
+        TETHERED_CALL_ACTIVE(-6),
+        ERROR_UNSPECIFIED(0xFFFF),
+
+        // Errors generated by the Framework
+        // specified here
+        UNKNOWN(0x10000),
+        RADIO_NOT_AVAILABLE(0x10001),
+        UNACCEPTABLE_NETWORK_PARAMETER(0x10002);
+
+        private final int mErrorCode;
+        private static final HashMap<Integer, FailCause> sErrorCodeToFailCauseMap;
+        static {
+            sErrorCodeToFailCauseMap = new HashMap<Integer, FailCause>();
+            for (FailCause fc : values()) {
+                sErrorCodeToFailCauseMap.put(fc.ordinal(), fc);
+            }
+        }
+
+        FailCause(int errorCode) {
+            mErrorCode = errorCode;
+        }
+
+        int getErrorCode() {
+            return mErrorCode;
+        }
 
         public boolean isPermanentFail() {
             return (this == OPERATOR_BARRED) || (this == MISSING_UNKNOWN_APN) ||
-                   (this == UNKNOWN_PDP_ADDRESS) || (this == USER_AUTHENTICATION) ||
+                   (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||
                    (this == SERVICE_OPTION_NOT_SUPPORTED) ||
                    (this == SERVICE_OPTION_NOT_SUBSCRIBED) || (this == NSAPI_IN_USE) ||
                    (this == PROTOCOL_ERRORS);
@@ -199,52 +188,21 @@
 
         public boolean isEventLoggable() {
             return (this == OPERATOR_BARRED) || (this == INSUFFICIENT_RESOURCES) ||
-                    (this == UNKNOWN_PDP_ADDRESS) || (this == USER_AUTHENTICATION) ||
+                    (this == UNKNOWN_PDP_ADDRESS_TYPE) || (this == USER_AUTHENTICATION) ||
                     (this == ACTIVATION_REJECT_GGSN) || (this == ACTIVATION_REJECT_UNSPECIFIED) ||
                     (this == SERVICE_OPTION_NOT_SUBSCRIBED) ||
                     (this == SERVICE_OPTION_NOT_SUPPORTED) ||
                     (this == SERVICE_OPTION_OUT_OF_ORDER) || (this == NSAPI_IN_USE) ||
-                    (this == PROTOCOL_ERRORS);
+                    (this == PROTOCOL_ERRORS) ||
+                    (this == UNACCEPTABLE_NETWORK_PARAMETER);
         }
 
-        @Override
-        public String toString() {
-            switch (this) {
-            case NONE:
-                return "No Error";
-            case OPERATOR_BARRED:
-                return "Operator Barred";
-            case INSUFFICIENT_RESOURCES:
-                return "Insufficient Resources";
-            case MISSING_UNKNOWN_APN:
-                return "Missing / Unknown APN";
-            case UNKNOWN_PDP_ADDRESS:
-                return "Unknown PDP Address";
-            case USER_AUTHENTICATION:
-                return "Error User Authentication";
-            case ACTIVATION_REJECT_GGSN:
-                return "Activation Reject GGSN";
-            case ACTIVATION_REJECT_UNSPECIFIED:
-                return "Activation Reject unspecified";
-            case SERVICE_OPTION_NOT_SUPPORTED:
-                return "Data Not Supported";
-            case SERVICE_OPTION_NOT_SUBSCRIBED:
-                return "Data Not subscribed";
-            case SERVICE_OPTION_OUT_OF_ORDER:
-                return "Data Services Out of Order";
-            case NSAPI_IN_USE:
-                return "NSAPI in use";
-            case PROTOCOL_ERRORS:
-                return "Protocol Errors";
-            case REGISTRATION_FAIL:
-                return "Network Registration Failure";
-            case GPRS_REGISTRATION_FAIL:
-                return "Data Network Registration Failure";
-            case RADIO_NOT_AVAILABLE:
-                return "Radio Not Available";
-            default:
-                return "Unknown Data Error";
+        public static FailCause fromInt(int errorCode) {
+            FailCause fc = sErrorCodeToFailCauseMap.get(errorCode);
+            if (fc == null) {
+                fc = UNKNOWN;
             }
+            return fc;
         }
     }
 
@@ -274,12 +232,11 @@
     Object userData;
 
     //***** Abstract methods
+    @Override
     public abstract String toString();
 
     protected abstract void onConnect(ConnectionParams cp);
 
-    protected abstract FailCause getFailCauseFromRequest(int rilCause);
-
     protected abstract boolean isDnsOk(String[] domainNameServers);
 
     protected abstract void log(String s);
@@ -300,7 +257,7 @@
             addState(mActivatingState, mDefaultState);
             addState(mActiveState, mDefaultState);
             addState(mDisconnectingState, mDefaultState);
-            addState(mDisconnectingBadDnsState, mDefaultState);
+            addState(mDisconnectingErrorCreatingConnection, mDefaultState);
         setInitialState(mInactiveState);
         if (DBG) log("DataConnection constructor X");
     }
@@ -408,20 +365,26 @@
      * @return SetupResult.
      */
     private SetupResult onSetupConnectionCompleted(AsyncResult ar) {
-        SetupResult result;
-        String[] response = ((String[]) ar.result);
+        DataCallState response = (DataCallState) ar.result;
         ConnectionParams cp = (ConnectionParams) ar.userObj;
+        SetupResult result;
 
         if (ar.exception != null) {
-            if (DBG) log("DataConnection Init failed " + ar.exception);
+            if (DBG) {
+                log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception +
+                    " response=" + response);
+            }
 
             if (ar.exception instanceof CommandException
                     && ((CommandException) (ar.exception)).getCommandError()
                     == CommandException.Error.RADIO_NOT_AVAILABLE) {
                 result = SetupResult.ERR_BadCommand;
                 result.mFailCause = FailCause.RADIO_NOT_AVAILABLE;
+            } else if ((response == null) || (response.version < 4)) {
+                result = SetupResult.ERR_GetLastErrorFromRil;
             } else {
-                result = SetupResult.ERR_Other;
+                result = SetupResult.ERR_RilError;
+                result.mFailCause = FailCause.fromInt(response.status);
             }
         } else if (cp.tag != mTag) {
             if (DBG) {
@@ -429,69 +392,93 @@
             }
             result = SetupResult.ERR_Stale;
         } else {
-//            log("onSetupConnectionCompleted received " + response.length + " response strings:");
-//            for (int i = 0; i < response.length; i++) {
-//                log("  response[" + i + "]='" + response[i] + "'");
-//            }
+            log("onSetupConnectionCompleted received DataCallState: " + response);
 
             // Start with clean network properties and if we have
             // a failure we'll clear again at the bottom of this code.
             LinkProperties linkProperties = new LinkProperties();
-            if (response.length >= 2) {
-                cid = Integer.parseInt(response[0]);
-                String interfaceName = response[1];
-                result = SetupResult.SUCCESS;
-
+            if (response.status == FailCause.NONE.getErrorCode()) {
                 try {
-                    String prefix = "net." + interfaceName + ".";
-
-                    NetworkInterface networkInterface = NetworkInterface.getByName(interfaceName);
-                    linkProperties.setInterfaceName(interfaceName);
-
-                    // TODO: Get gateway and dns via RIL interface not property?
-                    String gatewayAddress = SystemProperties.get(prefix + "gw");
-                    linkProperties.setGateway(InetAddress.getByName(gatewayAddress));
-
-                    for (InterfaceAddress addr : networkInterface.getInterfaceAddresses()) {
-                        linkProperties.addLinkAddress(new LinkAddress(addr));
+                    cid = response.cid;
+                    linkProperties.setInterfaceName(response.ifname);
+                    for (String addr : response.addresses) {
+                        LinkAddress la;
+                        if (!InetAddress.isNumeric(addr)) {
+                            EventLogTags.writeBadIpAddress(addr);
+                            throw new UnknownHostException("Non-numeric ip addr=" + addr);
+                        }
+                        InetAddress ia = InetAddress.getByName(addr);
+                        if (ia instanceof Inet4Address) {
+                            la = new LinkAddress(ia, 32);
+                        } else {
+                            la = new LinkAddress(ia, 128);
+                        }
+                        linkProperties.addLinkAddress(la);
                     }
-                    // TODO: Get gateway and dns via RIL interface not property?
-                    String dnsServers[] = new String[2];
-                    dnsServers[0] = SystemProperties.get(prefix + "dns1");
-                    dnsServers[1] = SystemProperties.get(prefix + "dns2");
-                    if (isDnsOk(dnsServers)) {
-                        linkProperties.addDns(InetAddress.getByName(dnsServers[0]));
-                        linkProperties.addDns(InetAddress.getByName(dnsServers[1]));
+
+                    if (response.dnses.length != 0) {
+                        for (String addr : response.dnses) {
+                            if (!InetAddress.isNumeric(addr)) {
+                                EventLogTags.writePdpBadDnsAddress("dns=" + addr); 
+                                throw new UnknownHostException("Non-numeric dns addr=" + addr);
+                            }
+                            InetAddress ia = InetAddress.getByName(addr);
+                            linkProperties.addDns(ia);
+                        }
+                        result = SetupResult.SUCCESS;
                     } else {
-                        result = SetupResult.ERR_BadDns;
+                        String prefix = "net." + response.ifname + ".";
+
+                        String dnsServers[] = new String[2];
+                        dnsServers[0] = SystemProperties.get(prefix + "dns1");
+                        dnsServers[1] = SystemProperties.get(prefix + "dns2");
+                        if (isDnsOk(dnsServers)) {
+                            for (String dnsAddr : dnsServers) {
+                                if (!InetAddress.isNumeric(dnsAddr)) {
+                                    EventLogTags.writePdpBadDnsAddress("dnsAddr=" + dnsAddr);
+                                    throw new UnknownHostException("Non-numeric dns addr="
+                                                + dnsAddr);
+                                }
+                                InetAddress ia = InetAddress.getByName(dnsAddr);
+                                linkProperties.addDns(ia);
+                            }
+                            result = SetupResult.SUCCESS;
+                        } else {
+                            StringBuilder sb = new StringBuilder();
+                            for (String dnsAddr : dnsServers) {
+                                sb.append(dnsAddr);
+                                sb.append(" ");
+                            }
+                            EventLogTags.writePdpBadDnsAddress("Unacceptable dns addresses=" + sb);
+                            throw new UnknownHostException("Unacceptable dns addresses=" + sb);
+                        }
                     }
-                } catch (UnknownHostException e1) {
-                    log("onSetupCompleted: UnknowHostException " + e1);
-                    e1.printStackTrace();
-                    result = SetupResult.ERR_Other;
-                } catch (SocketException e2) {
-                    log("onSetupCompleted: SocketException " + e2);
-                    e2.printStackTrace();
-                    result = SetupResult.ERR_Other;
+                } catch (UnknownHostException e) {
+                    log("onSetupCompleted: UnknownHostException " + e);
+                    e.printStackTrace();
+                    result = SetupResult.ERR_UnacceptableParameter;
                 }
             } else {
-                log("onSetupCompleted: error; expected number of responses >= 2 was " +
-                        response.length);
-                result = SetupResult.ERR_Other;
+                if (response.version < 4) {
+                    result = SetupResult.ERR_GetLastErrorFromRil;
+                } else {
+                    result = SetupResult.ERR_RilError;
+                }
             }
 
             // An error occurred so clear properties
             if (result != SetupResult.SUCCESS) {
-                log("onSetupCompleted with an error clearing LinkProperties");
+                log("onSetupConnectionCompleted with an error, clearing LinkProperties");
                 linkProperties.clear();
             }
             mLinkProperties = linkProperties;
         }
 
         if (DBG) {
-            log("DataConnection setup result='" + result + "' on cid=" + cid);
+            log("onSetupConnectionCompleted: DataConnection setup result='"
+                    + result + "' on cid=" + cid);
             if (result == SetupResult.SUCCESS) {
-                log("LinkProperties: " + mLinkProperties.toString());
+                log("onSetupConnectionCompleted: LinkProperties: " + mLinkProperties.toString());
             }
         }
         return result;
@@ -640,6 +627,7 @@
                     cp = (ConnectionParams) ar.userObj;
 
                     SetupResult result = onSetupConnectionCompleted(ar);
+                    if (DBG) log("DcActivatingState onSetupConnectionCompleted result=" + result);
                     switch (result) {
                         case SUCCESS:
                             // All is well
@@ -653,26 +641,21 @@
                             mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
                             transitionTo(mInactiveState);
                             break;
-                        case ERR_BadDns:
-                            // Connection succeeded but DNS info is bad so disconnect
-                            StringBuilder dnsAddressesSb = new StringBuilder();
-                            for (InetAddress addr : mLinkProperties.getDnses()) {
-                                if (dnsAddressesSb.length() != 0) dnsAddressesSb.append(" ");
-                                dnsAddressesSb.append(addr.toString());
-                            }
-                            if (dnsAddressesSb.length() == 0) {
-                                dnsAddressesSb.append("no-dns-addresses");
-                            }
-                            EventLog.writeEvent(EventLogTags.PDP_BAD_DNS_ADDRESS,
-                                    dnsAddressesSb.toString());
+                        case ERR_UnacceptableParameter:
+                            // The addresses given from the RIL are bad
                             tearDownData(cp);
-                            transitionTo(mDisconnectingBadDnsState);
+                            transitionTo(mDisconnectingErrorCreatingConnection);
                             break;
-                        case ERR_Other:
-                            // Request the failure cause and process in this state
+                        case ERR_GetLastErrorFromRil:
+                            // Request failed and this is an old RIL
                             phone.mCM.getLastDataCallFailCause(
                                     obtainMessage(EVENT_GET_LAST_FAIL_DONE, cp));
                             break;
+                        case ERR_RilError:
+                            // Request failed and mFailCause has the reason
+                            mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
+                            transitionTo(mInactiveState);
+                            break;
                         case ERR_Stale:
                             // Request is stale, ignore.
                             break;
@@ -691,7 +674,7 @@
                         if (DBG) log("DcActivatingState msg.what=EVENT_GET_LAST_FAIL_DONE");
                         if (ar.exception == null) {
                             int rilFailCause = ((int[]) (ar.result))[0];
-                            cause = getFailCauseFromRequest(rilFailCause);
+                            cause = FailCause.fromInt(rilFailCause);
                         }
                         // Transition to inactive but send notifications after
                         // we've entered the mInactive state.
@@ -808,10 +791,9 @@
     private DcDisconnectingState mDisconnectingState = new DcDisconnectingState();
 
     /**
-     * The state machine is disconnecting after a bad dns setup
-     * was found in mInactivatingState.
+     * The state machine is disconnecting after an creating a connection.
      */
-    private class DcDisconnectingBadDnsState extends HierarchicalState {
+    private class DcDisconnectionErrorCreatingConnection extends HierarchicalState {
         @Override protected boolean processMessage(Message msg) {
             boolean retVal;
 
@@ -820,27 +802,38 @@
                     AsyncResult ar = (AsyncResult) msg.obj;
                     ConnectionParams cp = (ConnectionParams) ar.userObj;
                     if (cp.tag == mTag) {
-                        if (DBG) log("DcDisconnectingBadDnsState msg.what=EVENT_DEACTIVATE_DONE");
+                        if (DBG) {
+                            log("DcDisconnectionErrorCreatingConnection" +
+                                " msg.what=EVENT_DEACTIVATE_DONE");
+                        }
+
                         // Transition to inactive but send notifications after
                         // we've entered the mInactive state.
-                        mInactiveState.setEnterNotificationParams(cp, FailCause.UNKNOWN);
+                        mInactiveState.setEnterNotificationParams(cp,
+                                FailCause.UNACCEPTABLE_NETWORK_PARAMETER);
                         transitionTo(mInactiveState);
                     } else {
-                        if (DBG) log("DcDisconnectingBadDnsState EVENT_DEACTIVE_DONE stale dp.tag="
-                                + cp.tag + ", mTag=" + mTag);
+                        if (DBG) {
+                            log("DcDisconnectionErrorCreatingConnection EVENT_DEACTIVATE_DONE" +
+                                    " stale dp.tag=" + cp.tag + ", mTag=" + mTag);
+                        }
                     }
                     retVal = true;
                     break;
 
                 default:
-                    if (DBG) log("DcDisconnectingBadDnsState not handled msg.what=" + msg.what);
+                    if (DBG) {
+                        log("DcDisconnectionErrorCreatingConnection not handled msg.what="
+                                + msg.what);
+                    }
                     retVal = false;
                     break;
             }
             return retVal;
         }
     }
-    private DcDisconnectingBadDnsState mDisconnectingBadDnsState = new DcDisconnectingBadDnsState();
+    private DcDisconnectionErrorCreatingConnection mDisconnectingErrorCreatingConnection =
+                new DcDisconnectionErrorCreatingConnection();
 
     // ******* public interface
 
diff --git a/telephony/java/com/android/internal/telephony/EventLogTags.logtags b/telephony/java/com/android/internal/telephony/EventLogTags.logtags
index b06c27d..9be7b80 100644
--- a/telephony/java/com/android/internal/telephony/EventLogTags.logtags
+++ b/telephony/java/com/android/internal/telephony/EventLogTags.logtags
@@ -53,3 +53,6 @@
 
 # CDMA service state transition
 50116 cdma_service_state_change (oldState|1|5), (oldDataState|1|5), (newState|1|5), (newDataState|1|5)
+
+# Bad IP address
+50117 bad_ip_address (ip_address|3)
diff --git a/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl b/telephony/java/com/android/internal/telephony/OperatorInfo.aidl
similarity index 78%
rename from telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl
rename to telephony/java/com/android/internal/telephony/OperatorInfo.aidl
index d88d0b7..3ded8dd 100644
--- a/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.aidl
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony.gsm;
+package com.android.internal.telephony;
 
 /**
- * Used to indicate that the NetworkInfo object is parcelable to aidl.
- * This is a simple effort to make NetworkInfo parcelable rather than
+ * Used to indicate that the OperatorInfo object is parcelable to aidl.
+ * This is a simple effort to make OperatorInfo parcelable rather than
  * trying to make the conventional containing object (AsyncResult),
  * implement parcelable.  This functionality is needed for the
  * NetworkQueryService to fix 1128695
  */
-parcelable NetworkInfo;
+parcelable OperatorInfo;
diff --git a/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java
similarity index 78%
rename from telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java
rename to telephony/java/com/android/internal/telephony/OperatorInfo.java
index 04fd13e..1999cb3 100644
--- a/telephony/java/com/android/internal/telephony/gsm/NetworkInfo.java
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony.gsm;
+package com.android.internal.telephony;
 
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -22,7 +22,7 @@
 /**
  * {@hide}
  */
-public class NetworkInfo implements Parcelable {
+public class OperatorInfo implements Parcelable {
     public enum State {
         UNKNOWN,
         AVAILABLE,
@@ -30,11 +30,11 @@
         FORBIDDEN;
     }
 
-    String operatorAlphaLong;
-    String operatorAlphaShort;
-    String operatorNumeric;
+    private String operatorAlphaLong;
+    private String operatorAlphaShort;
+    private String operatorNumeric;
 
-    State state = State.UNKNOWN;
+    private State state = State.UNKNOWN;
 
 
     public String
@@ -57,7 +57,7 @@
         return state;
     }
 
-    NetworkInfo(String operatorAlphaLong,
+    OperatorInfo(String operatorAlphaLong,
                 String operatorAlphaShort,
                 String operatorNumeric,
                 State state) {
@@ -70,7 +70,7 @@
     }
 
 
-    public NetworkInfo(String operatorAlphaLong,
+    public OperatorInfo(String operatorAlphaLong,
                 String operatorAlphaShort,
                 String operatorNumeric,
                 String stateString) {
@@ -98,7 +98,7 @@
 
 
     public String toString() {
-        return "NetworkInfo " + operatorAlphaLong
+        return "OperatorInfo " + operatorAlphaLong
                 + "/" + operatorAlphaShort
                 + "/" + operatorNumeric
                 + "/" + state;
@@ -106,7 +106,7 @@
 
     /**
      * Parcelable interface implemented below.
-     * This is a simple effort to make NetworkInfo parcelable rather than
+     * This is a simple effort to make OperatorInfo parcelable rather than
      * trying to make the conventional containing object (AsyncResult),
      * implement parcelable.  This functionality is needed for the
      * NetworkQueryService to fix 1128695.
@@ -118,7 +118,7 @@
 
     /**
      * Implement the Parcelable interface.
-     * Method to serialize a NetworkInfo object.
+     * Method to serialize a OperatorInfo object.
      */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(operatorAlphaLong);
@@ -129,21 +129,21 @@
 
     /**
      * Implement the Parcelable interface
-     * Method to deserialize a NetworkInfo object, or an array thereof.
+     * Method to deserialize a OperatorInfo object, or an array thereof.
      */
-    public static final Creator<NetworkInfo> CREATOR =
-        new Creator<NetworkInfo>() {
-            public NetworkInfo createFromParcel(Parcel in) {
-                NetworkInfo netInfo = new NetworkInfo(
+    public static final Creator<OperatorInfo> CREATOR =
+        new Creator<OperatorInfo>() {
+            public OperatorInfo createFromParcel(Parcel in) {
+                OperatorInfo opInfo = new OperatorInfo(
                         in.readString(), /*operatorAlphaLong*/
                         in.readString(), /*operatorAlphaShort*/
                         in.readString(), /*operatorNumeric*/
                         (State) in.readSerializable()); /*state*/
-                return netInfo;
+                return opInfo;
             }
 
-            public NetworkInfo[] newArray(int size) {
-                return new NetworkInfo[size];
+            public OperatorInfo[] newArray(int size) {
+                return new OperatorInfo[size];
             }
         };
 }
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index d0b5540..e2896d9b 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -27,7 +27,6 @@
 import android.telephony.SignalStrength;
 
 import com.android.internal.telephony.DataConnection;
-import com.android.internal.telephony.gsm.NetworkInfo;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 
 import java.util.List;
@@ -1056,7 +1055,7 @@
      * one of the following members:.<p>
      *<ul>
      * <li><code>response.obj.result</code> will be a <code>List</code> of
-     * <code>com.android.internal.telephony.gsm.NetworkInfo</code> objects, or</li>
+     * <code>OperatorInfo</code> objects, or</li>
      * <li><code>response.obj.exception</code> will be set with an exception
      * on failure.</li>
      * </ul>
@@ -1070,8 +1069,7 @@
      * @param response The message to dispatch when the network selection
      * is complete.
      *
-     * @see #selectNetworkManually(com.android.internal.telephony.gsm.NetworkInfo,
-     * android.os.Message )
+     * @see #selectNetworkManually(OperatorInfo, android.os.Message )
      */
     void setNetworkSelectionModeAutomatic(Message response);
 
@@ -1083,7 +1081,7 @@
      *
      * @see #setNetworkSelectionModeAutomatic(Message)
      */
-    void selectNetworkManually(NetworkInfo network,
+    void selectNetworkManually(OperatorInfo network,
                             Message response);
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index 219efbb..15b23bb 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -32,7 +32,6 @@
 
 import com.android.internal.telephony.cdma.CDMAPhone;
 import com.android.internal.telephony.gsm.GSMPhone;
-import com.android.internal.telephony.gsm.NetworkInfo;
 import com.android.internal.telephony.test.SimulatedRadioControl;
 
 import java.util.List;
@@ -550,7 +549,7 @@
         mActivePhone.setNetworkSelectionModeAutomatic(response);
     }
 
-    public void selectNetworkManually(NetworkInfo network, Message response) {
+    public void selectNetworkManually(OperatorInfo network, Message response) {
         mActivePhone.selectNetworkManually(network, response);
     }
 
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 7506bb1..21c88f1 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -50,12 +50,12 @@
 import com.android.internal.telephony.CallForwardInfo;
 import com.android.internal.telephony.CommandException;
 import com.android.internal.telephony.DataCallState;
-import com.android.internal.telephony.gsm.NetworkInfo;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.IccCardApplication;
 import com.android.internal.telephony.IccCardStatus;
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.SmsResponse;
 import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
@@ -2139,7 +2139,7 @@
             case RIL_REQUEST_DTMF: ret =  responseVoid(p); break;
             case RIL_REQUEST_SEND_SMS: ret =  responseSMS(p); break;
             case RIL_REQUEST_SEND_SMS_EXPECT_MORE: ret =  responseSMS(p); break;
-            case RIL_REQUEST_SETUP_DATA_CALL: ret =  responseStrings(p); break;
+            case RIL_REQUEST_SETUP_DATA_CALL: ret =  responseSetupDataCall(p); break;
             case RIL_REQUEST_SIM_IO: ret =  responseICC_IO(p); break;
             case RIL_REQUEST_SEND_USSD: ret =  responseVoid(p); break;
             case RIL_REQUEST_CANCEL_USSD: ret =  responseVoid(p); break;
@@ -2160,7 +2160,7 @@
             case RIL_REQUEST_QUERY_NETWORK_SELECTION_MODE: ret =  responseInts(p); break;
             case RIL_REQUEST_SET_NETWORK_SELECTION_AUTOMATIC: ret =  responseVoid(p); break;
             case RIL_REQUEST_SET_NETWORK_SELECTION_MANUAL: ret =  responseVoid(p); break;
-            case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS : ret =  responseNetworkInfos(p); break;
+            case RIL_REQUEST_QUERY_AVAILABLE_NETWORKS : ret =  responseOperatorInfos(p); break;
             case RIL_REQUEST_DTMF_START: ret =  responseVoid(p); break;
             case RIL_REQUEST_DTMF_STOP: ret =  responseVoid(p); break;
             case RIL_REQUEST_BASEBAND_VERSION: ret =  responseString(p); break;
@@ -2897,37 +2897,95 @@
         return response;
     }
 
-    private Object
-    responseDataCallList(Parcel p) {
-        int num;
-        ArrayList<DataCallState> response;
+    private DataCallState getDataCallState(Parcel p, int version) {
+        DataCallState dataCall = new DataCallState();
 
-        num = p.readInt();
-        response = new ArrayList<DataCallState>(num);
-
-        for (int i = 0; i < num; i++) {
-            DataCallState dataCall = new DataCallState();
-
+        dataCall.version = version;
+        if (version < 5) {
             dataCall.cid = p.readInt();
             dataCall.active = p.readInt();
             dataCall.type = p.readString();
-            dataCall.apn = p.readString();
-            String address = p.readString();
-            if (address != null) {
-                address = address.split(" ")[0];
+            p.readString(); // Ignore apn
+            String addresses = p.readString();
+            if (addresses != null) {
+                dataCall.addresses = addresses.split(" ");
             }
-            dataCall.address = address;
+        } else {
+            dataCall.status = p.readInt();
+            dataCall.cid = p.readInt();
+            dataCall.active = p.readInt();
+            dataCall.type = p.readString();
+            dataCall.ifname = p.readString();
+            String addresses = p.readString();
+            if (addresses != null) {
+                dataCall.addresses = addresses.split(" ");
+            }
+            String dnses = p.readString();
+            if (addresses != null) {
+                dataCall.dnses = dnses.split(" ");
+            }
+        }
+        return dataCall;
+    }
 
-            response.add(dataCall);
+    private Object
+    responseDataCallList(Parcel p) {
+        ArrayList<DataCallState> response;
+
+        int ver = p.readInt();
+        int num = p.readInt();
+        Log.d(LOG_TAG, "responseDataCallList ver=" + ver + " num=" + num);
+
+        response = new ArrayList<DataCallState>(num);
+        for (int i = 0; i < num; i++) {
+            response.add(getDataCallState(p, ver));
         }
 
         return response;
     }
 
     private Object
-    responseNetworkInfos(Parcel p) {
+    responseSetupDataCall(Parcel p) {
+        int ver = p.readInt();
+        int num = p.readInt();
+        Log.d(LOG_TAG, "responseSetupDataCall ver=" + ver + " num=" + num);
+
+        DataCallState dataCall;
+
+        if (ver < 5) {
+            if (num != 3) {
+                throw new RuntimeException(
+                        "RIL_REQUEST_SETUP_DATA_CALL response expecting 3 strings got " + num);
+            }
+            dataCall = new DataCallState();
+            dataCall.cid = Integer.parseInt(p.readString());
+            dataCall.ifname = p.readString();
+            if (dataCall.ifname == null) {
+                throw new RuntimeException(
+                        "RIL_REQUEST_SETUP_DATA_CALL response ifname");
+            }
+            String addresses = p.readString();
+            if (addresses == null) {
+                throw new RuntimeException(
+                "RIL_REQUEST_SETUP_DATA_CALL response no addresses");
+            }
+            dataCall.addresses = addresses.split(" ");
+        } else {
+            if (num != 1) {
+                throw new RuntimeException(
+                        "RIL_REQUEST_SETUP_DATA_CALL response expecting 1 RIL_Data_Call_response_v5"
+                        + " got " + num);
+            }
+            dataCall = getDataCallState(p, ver);
+        }
+
+        return dataCall;
+    }
+
+    private Object
+    responseOperatorInfos(Parcel p) {
         String strings[] = (String [])responseStrings(p);
-        ArrayList<NetworkInfo> ret;
+        ArrayList<OperatorInfo> ret;
 
         if (strings.length % 4 != 0) {
             throw new RuntimeException(
@@ -2935,11 +2993,11 @@
                 + strings.length + " strings, expected multible of 4");
         }
 
-        ret = new ArrayList<NetworkInfo>(strings.length / 4);
+        ret = new ArrayList<OperatorInfo>(strings.length / 4);
 
         for (int i = 0 ; i < strings.length ; i += 4) {
             ret.add (
-                new NetworkInfo(
+                new OperatorInfo(
                     strings[i+0],
                     strings[i+1],
                     strings[i+2],
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index a1d5121..3131ebe 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -56,6 +56,7 @@
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.PhoneNotifier;
@@ -483,7 +484,7 @@
     }
 
     public void
-    selectNetworkManually(com.android.internal.telephony.gsm.NetworkInfo network,
+    selectNetworkManually(OperatorInfo network,
             Message response) {
         Log.e(LOG_TAG, "selectNetworkManually: not possible in CDMA");
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
index 1b2c918..1a0dbc2 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java
@@ -20,6 +20,7 @@
 import android.util.Log;
 
 import com.android.internal.telephony.DataConnection;
+import com.android.internal.telephony.DataConnection.FailCause;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.RetryManager;
@@ -31,13 +32,6 @@
 
     private static final String LOG_TAG = "CDMA";
 
-    /** Fail cause of last Data Call activate from RIL_LastDataCallActivateFailCause */
-    private final static int PS_NET_DOWN_REASON_OPERATOR_DETERMINED_BARRING         = 8;
-    private final static int PS_NET_DOWN_REASON_AUTH_FAILED                         = 29;
-    private final static int PS_NET_DOWN_REASON_OPTION_NOT_SUPPORTED                = 32;
-    private final static int PS_NET_DOWN_REASON_OPTION_UNSUBSCRIBED                 = 33;
-
-
     // ***** Constructor
     private CdmaDataConnection(CDMAPhone phone, String name, RetryManager rm) {
         super(phone, name, rm);
@@ -104,29 +98,6 @@
     }
 
     @Override
-    protected FailCause getFailCauseFromRequest(int rilCause) {
-        FailCause cause;
-
-        switch (rilCause) {
-            case PS_NET_DOWN_REASON_OPERATOR_DETERMINED_BARRING:
-                cause = FailCause.OPERATOR_BARRED;
-                break;
-            case PS_NET_DOWN_REASON_AUTH_FAILED:
-                cause = FailCause.USER_AUTHENTICATION;
-                break;
-            case PS_NET_DOWN_REASON_OPTION_NOT_SUPPORTED:
-                cause = FailCause.SERVICE_OPTION_NOT_SUPPORTED;
-                break;
-            case PS_NET_DOWN_REASON_OPTION_UNSUBSCRIBED:
-                cause = FailCause.SERVICE_OPTION_NOT_SUBSCRIBED;
-                break;
-            default:
-                cause = FailCause.UNKNOWN;
-        }
-        return cause;
-    }
-
-    @Override
     protected boolean isDnsOk(String[] domainNameServers) {
         if ((NULL_IP.equals(domainNameServers[0])
                 && NULL_IP.equals(domainNameServers[1])
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index e8ad0f2..c17197e 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -62,6 +62,7 @@
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.PhoneNotifier;
@@ -1025,19 +1026,19 @@
     }
 
     public void
-    selectNetworkManually(com.android.internal.telephony.gsm.NetworkInfo network,
+    selectNetworkManually(OperatorInfo network,
             Message response) {
         // wrap the response message in our own message along with
         // the operator's id.
         NetworkSelectMessage nsm = new NetworkSelectMessage();
         nsm.message = response;
-        nsm.operatorNumeric = network.operatorNumeric;
-        nsm.operatorAlphaLong = network.operatorAlphaLong;
+        nsm.operatorNumeric = network.getOperatorNumeric();
+        nsm.operatorAlphaLong = network.getOperatorAlphaLong();
 
         // get the message
         Message msg = obtainMessage(EVENT_SET_NETWORK_MANUAL_COMPLETE, nsm);
 
-        mCM.setNetworkSelectionModeManual(network.operatorNumeric, msg);
+        mCM.setNetworkSelectionModeManual(network.getOperatorNumeric(), msg);
     }
 
     public void
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
index b947020..1d60bda 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java
@@ -33,24 +33,6 @@
 
     private static final String LOG_TAG = "GSM";
 
-    /** Fail cause of last PDP activate, from RIL_LastPDPActivateFailCause */
-    private static final int PDP_FAIL_OPERATOR_BARRED = 0x08;
-    private static final int PDP_FAIL_INSUFFICIENT_RESOURCES = 0x1A;
-    private static final int PDP_FAIL_MISSING_UKNOWN_APN = 0x1B;
-    private static final int PDP_FAIL_UNKNOWN_PDP_ADDRESS_TYPE = 0x1C;
-    private static final int PDP_FAIL_USER_AUTHENTICATION = 0x1D;
-    private static final int PDP_FAIL_ACTIVATION_REJECT_GGSN = 0x1E;
-    private static final int PDP_FAIL_ACTIVATION_REJECT_UNSPECIFIED = 0x1F;
-    private static final int PDP_FAIL_SERVICE_OPTION_NOT_SUPPORTED = 0x20;
-    private static final int PDP_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED = 0x21;
-    private static final int PDP_FAIL_SERVICE_OPTION_OUT_OF_ORDER = 0x22;
-    private static final int PDP_FAIL_NSAPI_IN_USE      = 0x23;
-    private static final int PDP_FAIL_PROTOCOL_ERRORS   = 0x6F;
-    private static final int PDP_FAIL_ERROR_UNSPECIFIED = 0xffff;
-
-    private static final int PDP_FAIL_REGISTRATION_FAIL = -1;
-    private static final int PDP_FAIL_GPRS_REGISTRATION_FAIL = -2;
-
     //***** Instance Variables
     private ApnSetting apn;
 
@@ -132,62 +114,6 @@
     }
 
     @Override
-    protected FailCause getFailCauseFromRequest(int rilCause) {
-        FailCause cause;
-
-        switch (rilCause) {
-            case PDP_FAIL_OPERATOR_BARRED:
-                cause = FailCause.OPERATOR_BARRED;
-                break;
-            case PDP_FAIL_INSUFFICIENT_RESOURCES:
-                cause = FailCause.INSUFFICIENT_RESOURCES;
-                break;
-            case PDP_FAIL_MISSING_UKNOWN_APN:
-                cause = FailCause.MISSING_UNKNOWN_APN;
-                break;
-            case PDP_FAIL_UNKNOWN_PDP_ADDRESS_TYPE:
-                cause = FailCause.UNKNOWN_PDP_ADDRESS;
-                break;
-            case PDP_FAIL_USER_AUTHENTICATION:
-                cause = FailCause.USER_AUTHENTICATION;
-                break;
-            case PDP_FAIL_ACTIVATION_REJECT_GGSN:
-                cause = FailCause.ACTIVATION_REJECT_GGSN;
-                break;
-            case PDP_FAIL_ACTIVATION_REJECT_UNSPECIFIED:
-                cause = FailCause.ACTIVATION_REJECT_UNSPECIFIED;
-                break;
-            case PDP_FAIL_SERVICE_OPTION_OUT_OF_ORDER:
-                cause = FailCause.SERVICE_OPTION_OUT_OF_ORDER;
-                break;
-            case PDP_FAIL_SERVICE_OPTION_NOT_SUPPORTED:
-                cause = FailCause.SERVICE_OPTION_NOT_SUPPORTED;
-                break;
-            case PDP_FAIL_SERVICE_OPTION_NOT_SUBSCRIBED:
-                cause = FailCause.SERVICE_OPTION_NOT_SUBSCRIBED;
-                break;
-            case PDP_FAIL_NSAPI_IN_USE:
-                cause = FailCause.NSAPI_IN_USE;
-                break;
-            case PDP_FAIL_PROTOCOL_ERRORS:
-                cause = FailCause.PROTOCOL_ERRORS;
-                break;
-            case PDP_FAIL_ERROR_UNSPECIFIED:
-                cause = FailCause.UNKNOWN;
-                break;
-            case PDP_FAIL_REGISTRATION_FAIL:
-                cause = FailCause.REGISTRATION_FAIL;
-                break;
-            case PDP_FAIL_GPRS_REGISTRATION_FAIL:
-                cause = FailCause.GPRS_REGISTRATION_FAIL;
-                break;
-            default:
-                cause = FailCause.UNKNOWN;
-        }
-        return cause;
-    }
-
-    @Override
     protected boolean isDnsOk(String[] domainNameServers) {
         if (NULL_IP.equals(domainNameServers[0]) && NULL_IP.equals(domainNameServers[1])
                 && !((GSMPhone) phone).isDnsCheckDisabled()) {
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
index db9e35a..9dfc015 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhoneBase.java
@@ -38,6 +38,7 @@
 import com.android.internal.telephony.IccPhoneBookInterfaceManager;
 import com.android.internal.telephony.IccSmsInterfaceManager;
 import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneBase;
 import com.android.internal.telephony.PhoneNotifier;
@@ -347,7 +348,7 @@
     }
 
     public void selectNetworkManually(
-            com.android.internal.telephony.gsm.NetworkInfo network,
+            OperatorInfo network,
             Message response) {
     }