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 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); } }
/** * 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). */ 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"; private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec."; private static final String USB_TYPEC_STATE = "sys.usb.typec.state"; // 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 fileIsRootWritable(String path) { try { // If the file is user writable, then it is root writable. return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0; } catch (ErrnoException e) { return false; } } private static boolean canChangeMode(File portDir) { return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath()); } private static boolean canChangePowerRole(File portDir) { return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath()); } private static boolean canChangeDataRole(File portDir) { return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath()); } 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 waitForState(String property, String state) { // wait for the transition to complete. // give up after 5 seconds. // 5 seconds is probably too long, but we have seen hardware that takes // over 3 seconds to change states. String value = null; for (int i = 0; i < 100; i++) { // State transition is done when property is set to the new configuration value = SystemProperties.get(property); if (state.equals(value)) return true; SystemClock.sleep(50); } Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value); return false; } private static String propertyFromFilename(String filename) { return USB_TYPEC_PROP_PREFIX + filename; } private static boolean writeFile(File dir, String filename, String contents) { SystemProperties.set(propertyFromFilename(filename), contents); return waitForState(USB_TYPEC_STATE, contents); } 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; } } }
// 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; } } }
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); } }