diff options
-rw-r--r-- | core/java/android/hardware/usb/IUsbManager.aidl | 11 | ||||
-rw-r--r-- | core/java/android/hardware/usb/UsbManager.java | 106 | ||||
-rw-r--r-- | core/java/android/hardware/usb/UsbPort.aidl | 19 | ||||
-rw-r--r-- | core/java/android/hardware/usb/UsbPort.java | 238 | ||||
-rw-r--r-- | core/java/android/hardware/usb/UsbPortStatus.aidl | 19 | ||||
-rw-r--r-- | core/java/android/hardware/usb/UsbPortStatus.java | 144 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | services/usb/java/com/android/server/usb/UsbPortManager.java | 753 | ||||
-rw-r--r-- | services/usb/java/com/android/server/usb/UsbService.java | 274 |
9 files changed, 1548 insertions, 17 deletions
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 @@ package android.hardware.usb; 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 @@ interface IUsbManager /* 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 class UsbManager { 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 class UsbManager { 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 @@ public class UsbManager { 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.IUsbManager; 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 android.util.SparseArray; 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 @@ public class UsbService extends IUsbManager.Stub { private UsbDeviceManager mDeviceManager; private UsbHostManager mHostManager; + private UsbPortManager mPortManager; private final UsbAlsaManager mAlsaManager; private final Object mLock = new Object(); @@ -110,6 +115,9 @@ public class UsbService extends IUsbManager.Stub { 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 @@ public class UsbService extends IUsbManager.Stub { if (mHostManager != null) { mHostManager.systemReady(); } + if (mPortManager != null) { + mPortManager.systemReady(); + } } public void bootCompleted() { @@ -346,29 +357,258 @@ public class UsbService extends IUsbManager.Stub { } @Override - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + public UsbPort[] getPorts() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println("USB Manager State:"); - pw.increaseIndent(); - if (mDeviceManager != null) { - mDeviceManager.dump(pw); + final long ident = Binder.clearCallingIdentity(); + try { + return mPortManager != null ? mPortManager.getPorts() : null; + } finally { + Binder.restoreCallingIdentity(ident); } - if (mHostManager != null) { - mHostManager.dump(pw); + } + + @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); } - 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 + ":"); + @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, " "); + 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); + } } |