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);
+ }
}