am 6e4997db: am 8bdd92d4: am 0cb1cfdc: am 76c4c666: Add USB port manager.

* commit '6e4997dbfb5f0a2abac99522a52b557172757ee2':
  Add USB port manager.
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 0fe112c..80c7b1a 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -19,6 +19,8 @@
 import android.app.PendingIntent;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 
@@ -108,4 +110,13 @@
 
     /* Clear public keys installed for secure USB debugging */
     void clearUsbDebuggingKeys();
+
+    /* Gets the list of USB ports. */
+    UsbPort[] getPorts();
+
+    /* Gets the status of the specified USB port. */
+    UsbPortStatus getPortStatus(in String portId);
+
+    /* Sets the port's current role. */
+    void setPortRoles(in String portId, int powerRole, int dataRole);
 }
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index f58b9d6..c88f213 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -17,6 +17,8 @@
 
 package android.hardware.usb;
 
+import com.android.internal.util.Preconditions;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Bundle;
@@ -74,6 +76,22 @@
     public static final String ACTION_USB_STATE =
             "android.hardware.usb.action.USB_STATE";
 
+    /**
+     * Broadcast Action: A broadcast for USB port changes.
+     *
+     * This intent is sent when a USB port is added, removed, or changes state.
+     * <ul>
+     * <li> {@link #EXTRA_PORT} containing the {@link android.hardware.usb.UsbPort}
+     * for the port.
+     * <li> {@link #EXTRA_PORT_STATUS} containing the {@link android.hardware.usb.UsbPortStatus}
+     * for the port, or null if the port has been removed
+     * </ul>
+     *
+     * @hide
+     */
+    public static final String ACTION_USB_PORT_CHANGED =
+            "android.hardware.usb.action.USB_PORT_CHANGED";
+
    /**
      * Broadcast Action:  A broadcast for USB device attached event.
      *
@@ -214,6 +232,23 @@
     public static final String USB_FUNCTION_ACCESSORY = "accessory";
 
     /**
+     * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
+     * containing the {@link UsbPort} object for the port.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PORT = "port";
+
+    /**
+     * Name of extra for {@link #ACTION_USB_PORT_CHANGED}
+     * containing the {@link UsbPortStatus} object for the port, or null if the port
+     * was removed.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PORT_STATUS = "portStatus";
+
+    /**
      * Name of extra for {@link #ACTION_USB_DEVICE_ATTACHED} and
      * {@link #ACTION_USB_DEVICE_DETACHED} broadcasts
      * containing the {@link UsbDevice} object for the device.
@@ -499,6 +534,77 @@
         return false;
     }
 
+    /**
+     * Returns a list of physical USB ports on the device.
+     * <p>
+     * This list is guaranteed to contain all dual-role USB Type C ports but it might
+     * be missing other ports depending on whether the kernel USB drivers have been
+     * updated to publish all of the device's ports through the new "dual_role_usb"
+     * device class (which supports all types of ports despite its name).
+     * </p>
+     *
+     * @return The list of USB ports, or null if none.
+     *
+     * @hide
+     */
+    public UsbPort[] getPorts() {
+        try {
+            return mService.getPorts();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getPorts", e);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the status of the specified USB port.
+     *
+     * @param port The port to query.
+     * @return The status of the specified USB port, or null if unknown.
+     *
+     * @hide
+     */
+    public UsbPortStatus getPortStatus(UsbPort port) {
+        Preconditions.checkNotNull(port, "port must not be null");
+
+        try {
+            return mService.getPortStatus(port.getId());
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getPortStatus", e);
+        }
+        return null;
+    }
+
+    /**
+     * Sets the desired role combination of the port.
+     * <p>
+     * The supported role combinations depend on what is connected to the port and may be
+     * determined by consulting
+     * {@link UsbPortStatus#isRoleCombinationSupported UsbPortStatus.isRoleCombinationSupported}.
+     * </p><p>
+     * Note: This function is asynchronous and may fail silently without applying
+     * the requested changes.  If this function does cause a status change to occur then
+     * a {@link #ACTION_USB_PORT_CHANGED} broadcast will be sent.
+     * </p>
+     *
+     * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE}
+     * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role.
+     * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST}
+     * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role.
+     *
+     * @hide
+     */
+    public void setPortRoles(UsbPort port, int powerRole, int dataRole) {
+        Preconditions.checkNotNull(port, "port must not be null");
+        UsbPort.checkRoles(powerRole, dataRole);
+
+        try {
+            mService.setPortRoles(port.getId(), powerRole, dataRole);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in setPortRole", e);
+        }
+    }
+
     /** @hide */
     public static String addFunction(String functions, String function) {
         if ("none".equals(functions)) {
diff --git a/core/java/android/hardware/usb/UsbPort.aidl b/core/java/android/hardware/usb/UsbPort.aidl
new file mode 100644
index 0000000..b7a7920
--- /dev/null
+++ b/core/java/android/hardware/usb/UsbPort.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+parcelable UsbPort;
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
new file mode 100644
index 0000000..c9a4e9b
--- /dev/null
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import com.android.internal.util.Preconditions;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a physical USB port and describes its characteristics.
+ * <p>
+ * This object is immutable.
+ * </p>
+ *
+ * @hide
+ */
+public final class UsbPort implements Parcelable {
+    private final String mId;
+    private final int mSupportedModes;
+
+    /**
+     * Mode bit: This USB port can act as a downstream facing port (host).
+     * <p>
+     * Implies that the port supports the {@link #POWER_ROLE_SOURCE} and {@link #DATA_ROLE_HOST}
+     * combination of roles (and possibly others as well).
+     * </p>
+     */
+    public static final int MODE_DFP = 1 << 0;
+
+    /**
+     * Mode bit: This USB port can act as an upstream facing port (device).
+     * <p>
+     * Implies that the port supports the {@link #POWER_ROLE_SINK} and {@link #DATA_ROLE_DEVICE}
+     * combination of roles (and possibly others as well).
+     * </p>
+     */
+    public static final int MODE_UFP = 1 << 1;
+
+    /**
+     * Mode bit: This USB port can act either as an downstream facing port (host) or as
+     * an upstream facing port (device).
+     * <p>
+     * Implies that the port supports the {@link #POWER_ROLE_SOURCE} and {@link #DATA_ROLE_HOST}
+     * combination of roles and the {@link #POWER_ROLE_SINK} and {@link #DATA_ROLE_DEVICE}
+     * combination of roles (and possibly others as well).
+     * </p>
+     */
+    public static final int MODE_DUAL = MODE_DFP | MODE_UFP;
+
+    /**
+     * Power role: This USB port can act as a source (provide power).
+     */
+    public static final int POWER_ROLE_SOURCE = 1;
+
+    /**
+     * Power role: This USB port can act as a sink (receive power).
+     */
+    public static final int POWER_ROLE_SINK = 2;
+
+    /**
+     * Data role: This USB port can act as a host (access data services).
+     */
+    public static final int DATA_ROLE_HOST = 1;
+
+    /**
+     * Data role: This USB port can act as a device (offer data services).
+     */
+    public static final int DATA_ROLE_DEVICE = 2;
+
+    private static final int NUM_DATA_ROLES = 3;
+
+    /** @hide */
+    public UsbPort(String id, int supportedModes) {
+        mId = id;
+        mSupportedModes = supportedModes;
+    }
+
+    /**
+     * Gets the unique id of the port.
+     *
+     * @return The unique id of the port; not intended for display.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
+     * Gets the supported modes of the port.
+     * <p>
+     * The actual mode of the port may vary depending on what is plugged into it.
+     * </p>
+     *
+     * @return The supported modes: one of {@link #MODE_DFP}, {@link #MODE_UFP}, or
+     * {@link #MODE_DUAL}.
+     */
+    public int getSupportedModes() {
+        return mSupportedModes;
+    }
+
+    /**
+     * Combines one power and one data role together into a unique value with
+     * exactly one bit set.  This can be used to efficiently determine whether
+     * a combination of roles is supported by testing whether that bit is present
+     * in a bit-field.
+     *
+     * @param powerRole The desired power role: {@link UsbPort#POWER_ROLE_SOURCE}
+     * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role.
+     * @param dataRole The desired data role: {@link UsbPort#DATA_ROLE_HOST}
+     * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role.
+     * @hide
+     */
+    public static int combineRolesAsBit(int powerRole, int dataRole) {
+        checkRoles(powerRole, dataRole);
+        final int index = powerRole * NUM_DATA_ROLES + dataRole;
+        return 1 << index;
+    }
+
+    /** @hide */
+    public static String modeToString(int mode) {
+        switch (mode) {
+            case 0:
+                return "none";
+            case MODE_DFP:
+                return "dfp";
+            case MODE_UFP:
+                return "ufp";
+            case MODE_DUAL:
+                return "dual";
+            default:
+                return Integer.toString(mode);
+        }
+    }
+
+    /** @hide */
+    public static String powerRoleToString(int role) {
+        switch (role) {
+            case 0:
+                return "no-power";
+            case POWER_ROLE_SOURCE:
+                return "source";
+            case POWER_ROLE_SINK:
+                return "sink";
+            default:
+                return Integer.toString(role);
+        }
+    }
+
+    /** @hide */
+    public static String dataRoleToString(int role) {
+        switch (role) {
+            case 0:
+                return "no-data";
+            case DATA_ROLE_HOST:
+                return "host";
+            case DATA_ROLE_DEVICE:
+                return "device";
+            default:
+                return Integer.toString(role);
+        }
+    }
+
+    /** @hide */
+    public static String roleCombinationsToString(int combo) {
+        StringBuilder result = new StringBuilder();
+        result.append("[");
+
+        boolean first = true;
+        while (combo != 0) {
+            final int index = Integer.numberOfTrailingZeros(combo);
+            combo &= ~(1 << index);
+            final int powerRole = index / NUM_DATA_ROLES;
+            final int dataRole = index % NUM_DATA_ROLES;
+            if (first) {
+                first = false;
+            } else {
+                result.append(", ");
+            }
+            result.append(powerRoleToString(powerRole));
+            result.append(':');
+            result.append(dataRoleToString(dataRole));
+        }
+
+        result.append("]");
+        return result.toString();
+    }
+
+    /** @hide */
+    public static void checkRoles(int powerRole, int dataRole) {
+        Preconditions.checkArgumentInRange(powerRole, 0, POWER_ROLE_SINK, "powerRole");
+        Preconditions.checkArgumentInRange(dataRole, 0, DATA_ROLE_DEVICE, "dataRole");
+    }
+
+    @Override
+    public String toString() {
+        return "UsbPort{id=" + mId + ", supportedModes=" + modeToString(mSupportedModes) + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        dest.writeInt(mSupportedModes);
+    }
+
+    public static final Parcelable.Creator<UsbPort> CREATOR =
+            new Parcelable.Creator<UsbPort>() {
+        @Override
+        public UsbPort createFromParcel(Parcel in) {
+            String id = in.readString();
+            int supportedModes = in.readInt();
+            return new UsbPort(id, supportedModes);
+        }
+
+        @Override
+        public UsbPort[] newArray(int size) {
+            return new UsbPort[size];
+        }
+    };
+}
diff --git a/core/java/android/hardware/usb/UsbPortStatus.aidl b/core/java/android/hardware/usb/UsbPortStatus.aidl
new file mode 100644
index 0000000..9a7e468
--- /dev/null
+++ b/core/java/android/hardware/usb/UsbPortStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+parcelable UsbPortStatus;
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
new file mode 100644
index 0000000..5c0e81a
--- /dev/null
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the status of a USB port.
+ * <p>
+ * This object is immutable.
+ * </p>
+ *
+ * @hide
+ */
+public final class UsbPortStatus implements Parcelable {
+    private final int mCurrentMode;
+    private final int mCurrentPowerRole;
+    private final int mCurrentDataRole;
+    private final int mSupportedRoleCombinations;
+
+    /** @hide */
+    public UsbPortStatus(int currentMode, int currentPowerRole, int currentDataRole,
+            int supportedRoleCombinations) {
+        mCurrentMode = currentMode;
+        mCurrentPowerRole = currentPowerRole;
+        mCurrentDataRole = currentDataRole;
+        mSupportedRoleCombinations = supportedRoleCombinations;
+    }
+
+    /**
+     * Returns true if there is anything connected to the port.
+     *
+     * @return True if there is anything connected to the port.
+     */
+    public boolean isConnected() {
+        return mCurrentMode != 0;
+    }
+
+    /**
+     * Gets the current mode of the port.
+     *
+     * @return The current mode: {@link UsbPort#MODE_DFP}, {@link UsbPort#MODE_UFP},
+     * or 0 if nothing is connected.
+     */
+    public int getCurrentMode() {
+        return mCurrentMode;
+    }
+
+    /**
+     * Gets the current power role of the port.
+     *
+     * @return The current power role: {@link UsbPort#POWER_ROLE_SOURCE},
+     * {@link UsbPort#POWER_ROLE_SINK}, or 0 if nothing is connected.
+     */
+    public int getCurrentPowerRole() {
+        return mCurrentPowerRole;
+    }
+
+    /**
+     * Gets the current data role of the port.
+     *
+     * @return The current data role: {@link UsbPort#DATA_ROLE_HOST},
+     * {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if nothing is connected.
+     */
+    public int getCurrentDataRole() {
+        return mCurrentDataRole;
+    }
+
+    /**
+     * Returns true if the specified power and data role combination is supported
+     * given what is currently connected to the port.
+     *
+     * @param powerRole The power role to check: {@link UsbPort#POWER_ROLE_SOURCE}
+     * or {@link UsbPort#POWER_ROLE_SINK}, or 0 if no power role.
+     * @param dataRole The data role to check: either {@link UsbPort#DATA_ROLE_HOST}
+     * or {@link UsbPort#DATA_ROLE_DEVICE}, or 0 if no data role.
+     */
+    public boolean isRoleCombinationSupported(int powerRole, int dataRole) {
+        return (mSupportedRoleCombinations &
+                UsbPort.combineRolesAsBit(powerRole, dataRole)) != 0;
+    }
+
+    /** @hide */
+    public int getSupportedRoleCombinations() {
+        return mSupportedRoleCombinations;
+    }
+
+    @Override
+    public String toString() {
+        return "UsbPortStatus{connected=" + isConnected()
+                + ", currentMode=" + UsbPort.modeToString(mCurrentMode)
+                + ", currentPowerRole=" + UsbPort.powerRoleToString(mCurrentPowerRole)
+                + ", currentDataRole=" + UsbPort.dataRoleToString(mCurrentDataRole)
+                + ", supportedRoleCombinations="
+                        + UsbPort.roleCombinationsToString(mSupportedRoleCombinations)
+                + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mCurrentMode);
+        dest.writeInt(mCurrentPowerRole);
+        dest.writeInt(mCurrentDataRole);
+        dest.writeInt(mSupportedRoleCombinations);
+    }
+
+    public static final Parcelable.Creator<UsbPortStatus> CREATOR =
+            new Parcelable.Creator<UsbPortStatus>() {
+        @Override
+        public UsbPortStatus createFromParcel(Parcel in) {
+            int currentMode = in.readInt();
+            int currentPowerRole = in.readInt();
+            int currentDataRole = in.readInt();
+            int supportedRoleCombinations = in.readInt();
+            return new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                    supportedRoleCombinations);
+        }
+
+        @Override
+        public UsbPortStatus[] newArray(int size) {
+            return new UsbPortStatus[size];
+        }
+    };
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d3117b9..062ae27 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -186,6 +186,7 @@
     <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
 
     <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
+    <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
     <protected-broadcast android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
diff --git a/services/usb/java/com/android/server/usb/UsbPortManager.java b/services/usb/java/com/android/server/usb/UsbPortManager.java
new file mode 100644
index 0000000..52abcfe
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/UsbPortManager.java
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
+
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+
+/**
+ * Allows trusted components to control the properties of physical USB ports
+ * via the "/sys/class/dual_role_usb" kernel interface.
+ * <p>
+ * Note: This interface may not be supported on all chipsets since the USB drivers
+ * must be changed to publish this information through the module.  At the moment
+ * we only need this for devices with USB Type C ports to allow the System UI to
+ * control USB charging and data direction.  On devices that do not support this
+ * interface the list of ports may incorrectly appear to be empty
+ * (but we don't care today).
+ * </p>
+ */
+public class UsbPortManager {
+    private static final String TAG = "UsbPortManager";
+
+    private static final int MSG_UPDATE_PORTS = 1;
+
+    // UEvent path to watch.
+    private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb";
+
+    // SysFS directory that contains USB ports as subdirectories.
+    private static final String SYSFS_CLASS = "/sys/class/dual_role_usb";
+
+    // SysFS file that contains a USB port's supported modes.  (read-only)
+    // Contents: "", "ufp", "dfp", or "ufp dfp".
+    private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes";
+
+    // SysFS file that contains a USB port's current mode.  (read-write if configurable)
+    // Contents: "", "ufp", or "dfp".
+    private static final String SYSFS_PORT_MODE = "mode";
+
+    // SysFS file that contains a USB port's current power role.  (read-write if configurable)
+    // Contents: "", "source", or "sink".
+    private static final String SYSFS_PORT_POWER_ROLE = "power_role";
+
+    // SysFS file that contains a USB port's current data role.  (read-write if configurable)
+    // Contents: "", "host", or "device".
+    private static final String SYSFS_PORT_DATA_ROLE = "data_role";
+
+    // Port modes: upstream facing port or downstream facing port.
+    private static final String PORT_MODE_DFP = "dfp";
+    private static final String PORT_MODE_UFP = "ufp";
+
+    // Port power roles: source or sink.
+    private static final String PORT_POWER_ROLE_SOURCE = "source";
+    private static final String PORT_POWER_ROLE_SINK = "sink";
+
+    // Port data roles: host or device.
+    private static final String PORT_DATA_ROLE_HOST = "host";
+    private static final String PORT_DATA_ROLE_DEVICE = "device";
+
+    // All non-trivial role combinations.
+    private static final int COMBO_SOURCE_HOST =
+            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST);
+    private static final int COMBO_SOURCE_DEVICE =
+            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE);
+    private static final int COMBO_SINK_HOST =
+            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST);
+    private static final int COMBO_SINK_DEVICE =
+            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE);
+
+    // The system context.
+    private final Context mContext;
+
+    // True if we have kernel support.
+    private final boolean mHaveKernelSupport;
+
+    // Mutex for all mutable shared state.
+    private final Object mLock = new Object();
+
+    // List of all ports, indexed by id.
+    // Ports may temporarily have different dispositions as they are added or removed
+    // but the class invariant is that this list will only contain ports with DISPOSITION_READY
+    // except while updatePortsLocked() is in progress.
+    private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>();
+
+    // List of all simulated ports, indexed by id.
+    private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts =
+            new ArrayMap<String, SimulatedPortInfo>();
+
+    public UsbPortManager(Context context) {
+        mContext = context;
+        mHaveKernelSupport = new File(SYSFS_CLASS).exists();
+    }
+
+    public void systemReady() {
+        mUEventObserver.startObserving(UEVENT_FILTER);
+        scheduleUpdatePorts();
+    }
+
+    public UsbPort[] getPorts() {
+        synchronized (mLock) {
+            final int count = mPorts.size();
+            final UsbPort[] result = new UsbPort[count];
+            for (int i = 0; i < count; i++) {
+                result[i] = mPorts.valueAt(i).mUsbPort;
+            }
+            return result;
+        }
+    }
+
+    public UsbPortStatus getPortStatus(String portId) {
+        synchronized (mLock) {
+            final PortInfo portInfo = mPorts.get(portId);
+            return portInfo != null ? portInfo.mUsbPortStatus : null;
+        }
+    }
+
+    public void setPortRoles(String portId, int newPowerRole, int newDataRole,
+            IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final PortInfo portInfo = mPorts.get(portId);
+            if (portInfo == null) {
+                if (pw != null) {
+                    pw.println("No such USB port: " + portId);
+                }
+                return;
+            }
+
+            // Check whether the new role is actually supported.
+            if (!portInfo.mUsbPortStatus.isRoleCombinationSupported(newPowerRole, newDataRole)) {
+                logAndPrint(Log.ERROR, pw, "Attempted to set USB port into unsupported "
+                        + "role combination: portId=" + portId
+                        + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
+                        + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
+                return;
+            }
+
+            // Check whether anything actually changed.
+            final int currentDataRole = portInfo.mUsbPortStatus.getCurrentDataRole();
+            final int currentPowerRole = portInfo.mUsbPortStatus.getCurrentPowerRole();
+            if (currentDataRole == newDataRole && currentPowerRole == newPowerRole) {
+                if (pw != null) {
+                    pw.println("No change.");
+                }
+                return;
+            }
+
+            // Determine whether we need to change the mode in order to accomplish this goal.
+            // We prefer not to do this since it's more likely to fail.
+            //
+            // Note: Arguably it might be worth allowing the client to influence this policy
+            // decision so that we could show more powerful developer facing UI but let's
+            // see how far we can get without having to do that.
+            final boolean canChangeMode = portInfo.mCanChangeMode;
+            final boolean canChangePowerRole = portInfo.mCanChangePowerRole;
+            final boolean canChangeDataRole = portInfo.mCanChangeDataRole;
+            final int currentMode = portInfo.mUsbPortStatus.getCurrentMode();
+            final int newMode;
+            if ((!canChangePowerRole && currentPowerRole != newPowerRole)
+                    || (!canChangeDataRole && currentDataRole != newDataRole)) {
+                if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE
+                        && newDataRole == UsbPort.DATA_ROLE_HOST) {
+                    newMode = UsbPort.MODE_DFP;
+                } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK
+                        && newDataRole == UsbPort.DATA_ROLE_DEVICE) {
+                    newMode = UsbPort.MODE_UFP;
+                } else {
+                    logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations "
+                            + "while attempting to change role: " + portInfo
+                            + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
+                            + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
+                    return;
+                }
+            } else {
+                newMode = currentMode;
+            }
+
+            // Make it happen.
+            logAndPrint(Log.INFO, pw, "Setting USB port mode and role: portId=" + portId
+                    + ", currentMode=" + UsbPort.modeToString(currentMode)
+                    + ", currentPowerRole=" + UsbPort.powerRoleToString(currentPowerRole)
+                    + ", currentDataRole=" + UsbPort.dataRoleToString(currentDataRole)
+                    + ", newMode=" + UsbPort.modeToString(newMode)
+                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
+                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
+
+            SimulatedPortInfo sim = mSimulatedPorts.get(portId);
+            if (sim != null) {
+                // Change simulated state.
+                sim.mCurrentMode = newMode;
+                sim.mCurrentPowerRole = newPowerRole;
+                sim.mCurrentDataRole = newDataRole;
+            } else if (mHaveKernelSupport) {
+                // Change actual state.
+                final File portDir = new File(SYSFS_CLASS, portId);
+                if (!portDir.exists()) {
+                    logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId);
+                    return;
+                }
+
+                if (currentMode != newMode) {
+                    // Changing the mode will have the side-effect of also changing
+                    // the power and data roles but it might take some time to apply
+                    // and the renegotiation might fail.  Due to limitations of the USB
+                    // hardware, we have no way of knowing whether it will work apriori
+                    // which is why we would prefer to set the power and data roles
+                    // directly instead.
+                    if (!writeFile(portDir, SYSFS_PORT_MODE,
+                            newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) {
+                        logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
+                                + "portId=" + portId
+                                + ", newMode=" + UsbPort.modeToString(newMode));
+                        return;
+                    }
+                } else {
+                    // Change power and data role independently as needed.
+                    if (currentPowerRole != newPowerRole) {
+                        if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE,
+                                newPowerRole == UsbPort.POWER_ROLE_SOURCE
+                                ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) {
+                            logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: "
+                                    + "portId=" + portId
+                                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole));
+                            return;
+                        }
+                    }
+                    if (currentDataRole != newDataRole) {
+                        if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE,
+                                newDataRole == UsbPort.DATA_ROLE_HOST
+                                ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) {
+                            logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: "
+                                    + "portId=" + portId
+                                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
+                            return;
+                        }
+                    }
+                }
+            }
+            updatePortsLocked(pw);
+        }
+    }
+
+    public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            if (mSimulatedPorts.containsKey(portId)) {
+                pw.println("Port with same name already exists.  Please remove it first.");
+                return;
+            }
+
+            pw.println("Adding simulated port: portId=" + portId
+                    + ", supportedModes=" + UsbPort.modeToString(supportedModes));
+            mSimulatedPorts.put(portId,
+                    new SimulatedPortInfo(portId, supportedModes));
+            updatePortsLocked(pw);
+        }
+    }
+
+    public void connectSimulatedPort(String portId, int mode, boolean canChangeMode,
+            int powerRole, boolean canChangePowerRole,
+            int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
+            if (portInfo == null) {
+                pw.println("Cannot connect simulated port which does not exist.");
+                return;
+            }
+
+            if (mode == 0 || powerRole == 0 || dataRole == 0) {
+                pw.println("Cannot connect simulated port in null mode, "
+                        + "power role, or data role.");
+                return;
+            }
+
+            if ((portInfo.mSupportedModes & mode) == 0) {
+                pw.println("Simulated port does not support mode: " + UsbPort.modeToString(mode));
+                return;
+            }
+
+            pw.println("Connecting simulated port: portId=" + portId
+                    + ", mode=" + UsbPort.modeToString(mode)
+                    + ", canChangeMode=" + canChangeMode
+                    + ", powerRole=" + UsbPort.powerRoleToString(powerRole)
+                    + ", canChangePowerRole=" + canChangePowerRole
+                    + ", dataRole=" + UsbPort.dataRoleToString(dataRole)
+                    + ", canChangeDataRole=" + canChangeDataRole);
+            portInfo.mCurrentMode = mode;
+            portInfo.mCanChangeMode = canChangeMode;
+            portInfo.mCurrentPowerRole = powerRole;
+            portInfo.mCanChangePowerRole = canChangePowerRole;
+            portInfo.mCurrentDataRole = dataRole;
+            portInfo.mCanChangeDataRole = canChangeDataRole;
+            updatePortsLocked(pw);
+        }
+    }
+
+    public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
+            if (portInfo == null) {
+                pw.println("Cannot disconnect simulated port which does not exist.");
+                return;
+            }
+
+            pw.println("Disconnecting simulated port: portId=" + portId);
+            portInfo.mCurrentMode = 0;
+            portInfo.mCanChangeMode = false;
+            portInfo.mCurrentPowerRole = 0;
+            portInfo.mCanChangePowerRole = false;
+            portInfo.mCurrentDataRole = 0;
+            portInfo.mCanChangeDataRole = false;
+            updatePortsLocked(pw);
+        }
+    }
+
+    public void removeSimulatedPort(String portId, IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            final int index = mSimulatedPorts.indexOfKey(portId);
+            if (index < 0) {
+                pw.println("Cannot remove simulated port which does not exist.");
+                return;
+            }
+
+            pw.println("Disconnecting simulated port: portId=" + portId);
+            mSimulatedPorts.removeAt(index);
+            updatePortsLocked(pw);
+        }
+    }
+
+    public void resetSimulation(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("Removing all simulated ports and ending simulation.");
+            if (!mSimulatedPorts.isEmpty()) {
+                mSimulatedPorts.clear();
+                updatePortsLocked(pw);
+            }
+        }
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.print("USB Port State:");
+            if (!mSimulatedPorts.isEmpty()) {
+                pw.print(" (simulation active; end with 'dumpsys usb reset')");
+            }
+            pw.println();
+
+            if (mPorts.isEmpty()) {
+                pw.println("  <no ports>");
+            } else {
+                for (PortInfo portInfo : mPorts.values()) {
+                    pw.println("  " + portInfo.mUsbPort.getId() + ": " + portInfo);
+                }
+            }
+        }
+    }
+
+    private void updatePortsLocked(IndentingPrintWriter pw) {
+        // Assume all ports are gone unless informed otherwise.
+        // Kind of pessimistic but simple.
+        for (int i = mPorts.size(); i-- > 0; ) {
+            mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED;
+        }
+
+        // Enumerate all extant ports.
+        if (!mSimulatedPorts.isEmpty()) {
+            final int count = mSimulatedPorts.size();
+            for (int i = 0; i < count; i++) {
+                final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i);
+                addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes,
+                        portInfo.mCurrentMode, portInfo.mCanChangeMode,
+                        portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole,
+                        portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw);
+            }
+        } else if (mHaveKernelSupport) {
+            final File[] portDirs = new File(SYSFS_CLASS).listFiles();
+            if (portDirs != null) {
+                for (File portDir : portDirs) {
+                    if (!portDir.isDirectory()) {
+                        continue;
+                    }
+
+                    // Parse the sysfs file contents.
+                    final String portId = portDir.getName();
+                    final int supportedModes = readSupportedModes(portDir);
+                    final int currentMode = readCurrentMode(portDir);
+                    final boolean canChangeMode = canChangeMode(portDir);
+                    final int currentPowerRole = readCurrentPowerRole(portDir);
+                    final boolean canChangePowerRole = canChangePowerRole(portDir);
+                    final int currentDataRole = readCurrentDataRole(portDir);
+                    final boolean canChangeDataRole = canChangeDataRole(portDir);
+                    addOrUpdatePortLocked(portId, supportedModes,
+                            currentMode, canChangeMode,
+                            currentPowerRole, canChangePowerRole,
+                            currentDataRole, canChangeDataRole, pw);
+                 }
+            }
+        }
+
+        // Process the updates.
+        // Once finished, the list of ports will only contain ports in DISPOSITION_READY.
+        for (int i = mPorts.size(); i-- > 0; ) {
+            final PortInfo portInfo = mPorts.valueAt(i);
+            switch (portInfo.mDisposition) {
+                case PortInfo.DISPOSITION_ADDED:
+                    handlePortAddedLocked(portInfo, pw);
+                    portInfo.mDisposition = PortInfo.DISPOSITION_READY;
+                    break;
+                case PortInfo.DISPOSITION_CHANGED:
+                    handlePortChangedLocked(portInfo, pw);
+                    portInfo.mDisposition = PortInfo.DISPOSITION_READY;
+                    break;
+                case PortInfo.DISPOSITION_REMOVED:
+                    mPorts.removeAt(i);
+                    portInfo.mUsbPortStatus = null; // must do this early
+                    handlePortRemovedLocked(portInfo, pw);
+                    break;
+            }
+        }
+    }
+
+    // Must only be called by updatePortsLocked.
+    private void addOrUpdatePortLocked(String portId, int supportedModes,
+            int currentMode, boolean canChangeMode,
+            int currentPowerRole, boolean canChangePowerRole,
+            int currentDataRole, boolean canChangeDataRole,
+            IndentingPrintWriter pw) {
+        // Only allow mode switch capability for dual role ports.
+        // Validate that the current mode matches the supported modes we expect.
+        if (supportedModes != UsbPort.MODE_DUAL) {
+            canChangeMode = false;
+            if (currentMode != 0 && currentMode != supportedModes) {
+                logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
+                        + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
+                        + ", currentMode=" + UsbPort.modeToString(currentMode));
+                currentMode = 0;
+            }
+        }
+
+        // Determine the supported role combinations.
+        // Note that the policy is designed to prefer setting the power and data
+        // role independently rather than changing the mode.
+        int supportedRoleCombinations = UsbPort.combineRolesAsBit(
+                currentPowerRole, currentDataRole);
+        if (currentMode != 0 && currentPowerRole != 0 && currentDataRole != 0) {
+            if (canChangePowerRole && canChangeDataRole) {
+                // Can change both power and data role independently.
+                // Assume all combinations are possible.
+                supportedRoleCombinations |=
+                        COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
+                                | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
+            } else if (canChangePowerRole) {
+                // Can only change power role.
+                // Assume data role must remain at its current value.
+                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
+                        UsbPort.POWER_ROLE_SOURCE, currentDataRole);
+                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
+                        UsbPort.POWER_ROLE_SINK, currentDataRole);
+            } else if (canChangeDataRole) {
+                // Can only change data role.
+                // Assume power role must remain at its current value.
+                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
+                        currentPowerRole, UsbPort.DATA_ROLE_HOST);
+                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
+                        currentPowerRole, UsbPort.DATA_ROLE_DEVICE);
+            } else if (canChangeMode) {
+                // Can only change the mode.
+                // Assume both standard UFP and DFP configurations will become available
+                // when this happens.
+                supportedRoleCombinations |= COMBO_SOURCE_HOST | COMBO_SINK_DEVICE;
+            }
+        }
+
+        // Update the port data structures.
+        PortInfo portInfo = mPorts.get(portId);
+        if (portInfo == null) {
+            portInfo = new PortInfo(portId, supportedModes);
+            portInfo.setStatus(currentMode, canChangeMode,
+                    currentPowerRole, canChangePowerRole,
+                    currentDataRole, canChangeDataRole,
+                    supportedRoleCombinations);
+            mPorts.put(portId, portInfo);
+        } else {
+            // Sanity check that ports aren't changing definition out from under us.
+            if (supportedModes != portInfo.mUsbPort.getSupportedModes()) {
+                logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from "
+                        + "USB port driver (should be immutable): "
+                        + "previous=" + UsbPort.modeToString(
+                                portInfo.mUsbPort.getSupportedModes())
+                        + ", current=" + UsbPort.modeToString(supportedModes));
+            }
+
+            if (portInfo.setStatus(currentMode, canChangeMode,
+                    currentPowerRole, canChangePowerRole,
+                    currentDataRole, canChangeDataRole,
+                    supportedRoleCombinations)) {
+                portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
+            } else {
+                portInfo.mDisposition = PortInfo.DISPOSITION_READY;
+            }
+        }
+    }
+
+    private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
+        logAndPrint(Log.INFO, pw, "USB port added: " + portInfo);
+        sendPortChangedBroadcastLocked(portInfo);
+    }
+
+    private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
+        logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
+        sendPortChangedBroadcastLocked(portInfo);
+    }
+
+    private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
+        logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
+        sendPortChangedBroadcastLocked(portInfo);
+    }
+
+    private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
+        final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort);
+        intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
+
+        // Guard against possible reentrance by posting the broadcast from the handler
+        // instead of from within the critical section.
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
+        });
+    }
+
+    private void scheduleUpdatePorts() {
+        if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) {
+            mHandler.sendEmptyMessage(MSG_UPDATE_PORTS);
+        }
+    }
+
+    private static int readSupportedModes(File portDir) {
+        int modes = 0;
+        final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES);
+        if (contents != null) {
+            if (contents.contains(PORT_MODE_DFP)) {
+                modes |= UsbPort.MODE_DFP;
+            }
+            if (contents.contains(PORT_MODE_UFP)) {
+                modes |= UsbPort.MODE_UFP;
+            }
+        }
+        return modes;
+    }
+
+    private static int readCurrentMode(File portDir) {
+        final String contents = readFile(portDir, SYSFS_PORT_MODE);
+        if (contents != null) {
+            if (contents.equals(PORT_MODE_DFP)) {
+                return UsbPort.MODE_DFP;
+            }
+            if (contents.equals(PORT_MODE_UFP)) {
+                return UsbPort.MODE_UFP;
+            }
+        }
+        return 0;
+    }
+
+    private static int readCurrentPowerRole(File portDir) {
+        final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE);
+        if (contents != null) {
+            if (contents.equals(PORT_POWER_ROLE_SOURCE)) {
+                return UsbPort.POWER_ROLE_SOURCE;
+            }
+            if (contents.equals(PORT_POWER_ROLE_SINK)) {
+                return UsbPort.POWER_ROLE_SINK;
+            }
+        }
+        return 0;
+    }
+
+    private static int readCurrentDataRole(File portDir) {
+        final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE);
+        if (contents != null) {
+            if (contents.equals(PORT_DATA_ROLE_HOST)) {
+                return UsbPort.DATA_ROLE_HOST;
+            }
+            if (contents.equals(PORT_DATA_ROLE_DEVICE)) {
+                return UsbPort.DATA_ROLE_DEVICE;
+            }
+        }
+        return 0;
+    }
+
+    private static boolean canChangeMode(File portDir) {
+        return new File(portDir, SYSFS_PORT_MODE).canWrite();
+    }
+
+    private static boolean canChangePowerRole(File portDir) {
+        return new File(portDir, SYSFS_PORT_POWER_ROLE).canWrite();
+    }
+
+    private static boolean canChangeDataRole(File portDir) {
+        return new File(portDir, SYSFS_PORT_DATA_ROLE).canWrite();
+    }
+
+    private static String readFile(File dir, String filename) {
+        final File file = new File(dir, filename);
+        try {
+            return IoUtils.readFileAsString(file.getAbsolutePath()).trim();
+        } catch (IOException ex) {
+            return null;
+        }
+    }
+
+    private static boolean writeFile(File dir, String filename, String contents) {
+        final File file = new File(dir, filename);
+        try {
+            try (FileWriter writer = new FileWriter(file)) {
+                writer.write(contents);
+            }
+            return true;
+        } catch (IOException ex) {
+            return false;
+        }
+    }
+
+    private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+        Slog.println(priority, TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+        }
+    }
+
+    private final Handler mHandler = new Handler(FgThread.get().getLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_PORTS: {
+                    synchronized (mLock) {
+                        updatePortsLocked(null);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    private final UEventObserver mUEventObserver = new UEventObserver() {
+        @Override
+        public void onUEvent(UEvent event) {
+            scheduleUpdatePorts();
+        }
+    };
+
+    /**
+     * Describes a USB port.
+     */
+    private static final class PortInfo {
+        public static final int DISPOSITION_ADDED = 0;
+        public static final int DISPOSITION_CHANGED = 1;
+        public static final int DISPOSITION_READY = 2;
+        public static final int DISPOSITION_REMOVED = 3;
+
+        public final UsbPort mUsbPort;
+        public UsbPortStatus mUsbPortStatus;
+        public boolean mCanChangeMode;
+        public boolean mCanChangePowerRole;
+        public boolean mCanChangeDataRole;
+        public int mDisposition; // default initialized to 0 which means added
+
+        public PortInfo(String portId, int supportedModes) {
+            mUsbPort = new UsbPort(portId, supportedModes);
+        }
+
+        public boolean setStatus(int currentMode, boolean canChangeMode,
+                int currentPowerRole, boolean canChangePowerRole,
+                int currentDataRole, boolean canChangeDataRole,
+                int supportedRoleCombinations) {
+            mCanChangeMode = canChangeMode;
+            mCanChangePowerRole = canChangePowerRole;
+            mCanChangeDataRole = canChangeDataRole;
+            if (mUsbPortStatus == null
+                    || mUsbPortStatus.getCurrentMode() != currentMode
+                    || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
+                    || mUsbPortStatus.getCurrentDataRole() != currentDataRole
+                    || mUsbPortStatus.getSupportedRoleCombinations()
+                            != supportedRoleCombinations) {
+                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
+                        supportedRoleCombinations);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            return "port=" + mUsbPort + ", status=" + mUsbPortStatus
+                    + ", canChangeMode=" + mCanChangeMode
+                    + ", canChangePowerRole=" + mCanChangePowerRole
+                    + ", canChangeDataRole=" + mCanChangeDataRole;
+        }
+    }
+
+    /**
+     * Describes a simulated USB port.
+     * Roughly mirrors the information we would ordinarily get from the kernel.
+     */
+    private static final class SimulatedPortInfo {
+        public final String mPortId;
+        public final int mSupportedModes;
+        public int mCurrentMode;
+        public boolean mCanChangeMode;
+        public int mCurrentPowerRole;
+        public boolean mCanChangePowerRole;
+        public int mCurrentDataRole;
+        public boolean mCanChangeDataRole;
+
+        public SimulatedPortInfo(String portId, int supportedModes) {
+            mPortId = portId;
+            mSupportedModes = supportedModes;
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d2ab0b8..f93a2ef 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -27,6 +27,9 @@
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
 import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
@@ -36,6 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
 import com.android.server.SystemService;
 
 import java.io.File;
@@ -78,6 +82,7 @@
 
     private UsbDeviceManager mDeviceManager;
     private UsbHostManager mHostManager;
+    private UsbPortManager mPortManager;
     private final UsbAlsaManager mAlsaManager;
 
     private final Object mLock = new Object();
@@ -110,6 +115,9 @@
         if (new File("/sys/class/android_usb").exists()) {
             mDeviceManager = new UsbDeviceManager(context, mAlsaManager);
         }
+        if (mHostManager != null || mDeviceManager != null) {
+            mPortManager = new UsbPortManager(context);
+        }
 
         setCurrentUser(UserHandle.USER_OWNER);
 
@@ -160,6 +168,9 @@
         if (mHostManager != null) {
             mHostManager.systemReady();
         }
+        if (mPortManager != null) {
+            mPortManager.systemReady();
+        }
     }
 
     public void bootCompleted() {
@@ -346,29 +357,258 @@
     }
 
     @Override
+    public UsbPort[] getPorts() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mPortManager != null ? mPortManager.getPorts() : null;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public UsbPortStatus getPortStatus(String portId) {
+        Preconditions.checkNotNull(portId, "portId must not be null");
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mPortManager != null ? mPortManager.getPortStatus(portId) : null;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void setPortRoles(String portId, int powerRole, int dataRole) {
+        Preconditions.checkNotNull(portId, "portId must not be null");
+        UsbPort.checkRoles(powerRole, dataRole);
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (mPortManager != null) {
+                mPortManager.setPortRoles(portId, powerRole, dataRole, null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
 
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-        pw.println("USB Manager State:");
-        pw.increaseIndent();
-        if (mDeviceManager != null) {
-            mDeviceManager.dump(pw);
-        }
-        if (mHostManager != null) {
-            mHostManager.dump(pw);
-        }
-        mAlsaManager.dump(pw);
-
-        synchronized (mLock) {
-            for (int i = 0; i < mSettingsByUser.size(); i++) {
-                final int userId = mSettingsByUser.keyAt(i);
-                final UsbSettingsManager settings = mSettingsByUser.valueAt(i);
-                pw.println("Settings for user " + userId + ":");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (args == null || args.length == 0 || "-a".equals(args[0])) {
+                pw.println("USB Manager State:");
                 pw.increaseIndent();
-                settings.dump(pw);
-                pw.decreaseIndent();
+                if (mDeviceManager != null) {
+                    mDeviceManager.dump(pw);
+                }
+                if (mHostManager != null) {
+                    mHostManager.dump(pw);
+                }
+                if (mPortManager != null) {
+                    mPortManager.dump(pw);
+                }
+                mAlsaManager.dump(pw);
+
+                synchronized (mLock) {
+                    for (int i = 0; i < mSettingsByUser.size(); i++) {
+                        final int userId = mSettingsByUser.keyAt(i);
+                        final UsbSettingsManager settings = mSettingsByUser.valueAt(i);
+                        pw.println("Settings for user " + userId + ":");
+                        pw.increaseIndent();
+                        settings.dump(pw);
+                        pw.decreaseIndent();
+                    }
+                }
+            } else if (args.length == 4 && "set-port-roles".equals(args[0])) {
+                final String portId = args[1];
+                final int powerRole;
+                switch (args[2]) {
+                    case "source":
+                        powerRole = UsbPort.POWER_ROLE_SOURCE;
+                        break;
+                    case "sink":
+                        powerRole = UsbPort.POWER_ROLE_SINK;
+                        break;
+                    case "no-power":
+                        powerRole = 0;
+                        break;
+                    default:
+                        pw.println("Invalid power role: " + args[2]);
+                        return;
+                }
+                final int dataRole;
+                switch (args[3]) {
+                    case "host":
+                        dataRole = UsbPort.DATA_ROLE_HOST;
+                        break;
+                    case "device":
+                        dataRole = UsbPort.DATA_ROLE_DEVICE;
+                        break;
+                    case "no-data":
+                        dataRole = 0;
+                        break;
+                    default:
+                        pw.println("Invalid data role: " + args[3]);
+                        return;
+                }
+                if (mPortManager != null) {
+                    mPortManager.setPortRoles(portId, powerRole, dataRole, pw);
+                    // Note: It might take some time for the side-effects of this operation
+                    // to be fully applied by the kernel since the driver may need to
+                    // renegotiate the USB port mode.  If this proves to be an issue
+                    // during debugging, it might be worth adding a sleep here before
+                    // dumping the new state.
+                    pw.println();
+                    mPortManager.dump(pw);
+                }
+            } else if (args.length == 3 && "add-port".equals(args[0])) {
+                final String portId = args[1];
+                final int supportedModes;
+                switch (args[2]) {
+                    case "ufp":
+                        supportedModes = UsbPort.MODE_UFP;
+                        break;
+                    case "dfp":
+                        supportedModes = UsbPort.MODE_DFP;
+                        break;
+                    case "dual":
+                        supportedModes = UsbPort.MODE_DUAL;
+                        break;
+                    case "none":
+                        supportedModes = 0;
+                        break;
+                    default:
+                        pw.println("Invalid mode: " + args[2]);
+                        return;
+                }
+                if (mPortManager != null) {
+                    mPortManager.addSimulatedPort(portId, supportedModes, pw);
+                    pw.println();
+                    mPortManager.dump(pw);
+                }
+            } else if (args.length == 5 && "connect-port".equals(args[0])) {
+                final String portId = args[1];
+                final int mode;
+                final boolean canChangeMode = args[2].endsWith("?");
+                switch (canChangeMode ? removeLastChar(args[2]) : args[2]) {
+                    case "ufp":
+                        mode = UsbPort.MODE_UFP;
+                        break;
+                    case "dfp":
+                        mode = UsbPort.MODE_DFP;
+                        break;
+                    default:
+                        pw.println("Invalid mode: " + args[2]);
+                        return;
+                }
+                final int powerRole;
+                final boolean canChangePowerRole = args[3].endsWith("?");
+                switch (canChangePowerRole ? removeLastChar(args[3]) : args[3]) {
+                    case "source":
+                        powerRole = UsbPort.POWER_ROLE_SOURCE;
+                        break;
+                    case "sink":
+                        powerRole = UsbPort.POWER_ROLE_SINK;
+                        break;
+                    default:
+                        pw.println("Invalid power role: " + args[3]);
+                        return;
+                }
+                final int dataRole;
+                final boolean canChangeDataRole = args[4].endsWith("?");
+                switch (canChangeDataRole ? removeLastChar(args[4]) : args[4]) {
+                    case "host":
+                        dataRole = UsbPort.DATA_ROLE_HOST;
+                        break;
+                    case "device":
+                        dataRole = UsbPort.DATA_ROLE_DEVICE;
+                        break;
+                    default:
+                        pw.println("Invalid data role: " + args[4]);
+                        return;
+                }
+                if (mPortManager != null) {
+                    mPortManager.connectSimulatedPort(portId, mode, canChangeMode,
+                            powerRole, canChangePowerRole, dataRole, canChangeDataRole, pw);
+                    pw.println();
+                    mPortManager.dump(pw);
+                }
+            } else if (args.length == 2 && "disconnect-port".equals(args[0])) {
+                final String portId = args[1];
+                if (mPortManager != null) {
+                    mPortManager.disconnectSimulatedPort(portId, pw);
+                    pw.println();
+                    mPortManager.dump(pw);
+                }
+            } else if (args.length == 2 && "remove-port".equals(args[0])) {
+                final String portId = args[1];
+                if (mPortManager != null) {
+                    mPortManager.removeSimulatedPort(portId, pw);
+                    pw.println();
+                    mPortManager.dump(pw);
+                }
+            } else if (args.length == 1 && "reset".equals(args[0])) {
+                if (mPortManager != null) {
+                    mPortManager.resetSimulation(pw);
+                    pw.println();
+                    mPortManager.dump(pw);
+                }
+            } else if (args.length == 1 && "ports".equals(args[0])) {
+                if (mPortManager != null) {
+                    mPortManager.dump(pw);
+                }
+            } else {
+                pw.println("Dump current USB state or issue command:");
+                pw.println("  ports");
+                pw.println("  set-port-roles <id> <source|sink|no-power> <host|device|no-data>");
+                pw.println("  add-port <id> <ufp|dfp|dual|none>");
+                pw.println("  connect-port <id> <ufp|dfp><?> <source|sink><?> <host|device><?>");
+                pw.println("    (add ? suffix if mode, power role, or data role can be changed)");
+                pw.println("  disconnect-port <id>");
+                pw.println("  remove-port <id>");
+                pw.println("  reset");
+                pw.println();
+                pw.println("Example USB type C port role switch:");
+                pw.println("  dumpsys usb set-port-roles \"default\" source device");
+                pw.println();
+                pw.println("Example USB type C port simulation with full capabilities:");
+                pw.println("  dumpsys usb add-port \"matrix\" dual");
+                pw.println("  dumpsys usb connect-port \"matrix\" ufp? sink? device?");
+                pw.println("  dumpsys usb ports");
+                pw.println("  dumpsys usb disconnect-port \"matrix\"");
+                pw.println("  dumpsys usb remove-port \"matrix\"");
+                pw.println("  dumpsys usb reset");
+                pw.println();
+                pw.println("Example USB type C port where only power role can be changed:");
+                pw.println("  dumpsys usb add-port \"matrix\" dual");
+                pw.println("  dumpsys usb connect-port \"matrix\" dfp source? host");
+                pw.println("  dumpsys usb reset");
+                pw.println();
+                pw.println("Example USB OTG port where id pin determines function:");
+                pw.println("  dumpsys usb add-port \"matrix\" dual");
+                pw.println("  dumpsys usb connect-port \"matrix\" dfp source host");
+                pw.println("  dumpsys usb reset");
+                pw.println();
+                pw.println("Example USB device-only port:");
+                pw.println("  dumpsys usb add-port \"matrix\" ufp");
+                pw.println("  dumpsys usb connect-port \"matrix\" ufp sink device");
+                pw.println("  dumpsys usb reset");
             }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
     }
+
+    private static final String removeLastChar(String value) {
+        return value.substring(0, value.length() - 1);
+    }
 }