1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.usb; 18 19 import com.android.internal.util.IndentingPrintWriter; 20 import com.android.server.FgThread; 21 22 import android.content.Context; 23 import android.content.Intent; 24 import android.hardware.usb.UsbManager; 25 import android.hardware.usb.UsbPort; 26 import android.hardware.usb.UsbPortStatus; 27 import android.os.Handler; 28 import android.os.Message; 29 import android.os.UEventObserver; 30 import android.os.UserHandle; 31 import android.util.ArrayMap; 32 import android.util.Log; 33 import android.util.Slog; 34 35 import java.io.File; 36 import java.io.FileWriter; 37 import java.io.IOException; 38 39 import libcore.io.IoUtils; 40 41 /** 42 * Allows trusted components to control the properties of physical USB ports 43 * via the "/sys/class/dual_role_usb" kernel interface. 44 * <p> 45 * Note: This interface may not be supported on all chipsets since the USB drivers 46 * must be changed to publish this information through the module. At the moment 47 * we only need this for devices with USB Type C ports to allow the System UI to 48 * control USB charging and data direction. On devices that do not support this 49 * interface the list of ports may incorrectly appear to be empty 50 * (but we don't care today). 51 * </p> 52 */ 53 public class UsbPortManager { 54 private static final String TAG = "UsbPortManager"; 55 56 private static final int MSG_UPDATE_PORTS = 1; 57 58 // UEvent path to watch. 59 private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb"; 60 61 // SysFS directory that contains USB ports as subdirectories. 62 private static final String SYSFS_CLASS = "/sys/class/dual_role_usb"; 63 64 // SysFS file that contains a USB port's supported modes. (read-only) 65 // Contents: "", "ufp", "dfp", or "ufp dfp". 66 private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes"; 67 68 // SysFS file that contains a USB port's current mode. (read-write if configurable) 69 // Contents: "", "ufp", or "dfp". 70 private static final String SYSFS_PORT_MODE = "mode"; 71 72 // SysFS file that contains a USB port's current power role. (read-write if configurable) 73 // Contents: "", "source", or "sink". 74 private static final String SYSFS_PORT_POWER_ROLE = "power_role"; 75 76 // SysFS file that contains a USB port's current data role. (read-write if configurable) 77 // Contents: "", "host", or "device". 78 private static final String SYSFS_PORT_DATA_ROLE = "data_role"; 79 80 // Port modes: upstream facing port or downstream facing port. 81 private static final String PORT_MODE_DFP = "dfp"; 82 private static final String PORT_MODE_UFP = "ufp"; 83 84 // Port power roles: source or sink. 85 private static final String PORT_POWER_ROLE_SOURCE = "source"; 86 private static final String PORT_POWER_ROLE_SINK = "sink"; 87 88 // Port data roles: host or device. 89 private static final String PORT_DATA_ROLE_HOST = "host"; 90 private static final String PORT_DATA_ROLE_DEVICE = "device"; 91 92 // All non-trivial role combinations. 93 private static final int COMBO_SOURCE_HOST = 94 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST); 95 private static final int COMBO_SOURCE_DEVICE = 96 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE); 97 private static final int COMBO_SINK_HOST = 98 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST); 99 private static final int COMBO_SINK_DEVICE = 100 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); 101 102 // The system context. 103 private final Context mContext; 104 105 // True if we have kernel support. 106 private final boolean mHaveKernelSupport; 107 108 // Mutex for all mutable shared state. 109 private final Object mLock = new Object(); 110 111 // List of all ports, indexed by id. 112 // Ports may temporarily have different dispositions as they are added or removed 113 // but the class invariant is that this list will only contain ports with DISPOSITION_READY 114 // except while updatePortsLocked() is in progress. 115 private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>(); 116 117 // List of all simulated ports, indexed by id. 118 private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts = 119 new ArrayMap<String, SimulatedPortInfo>(); 120 121 public UsbPortManager(Context context) { 122 mContext = context; 123 mHaveKernelSupport = new File(SYSFS_CLASS).exists(); 124 } 125 126 public void systemReady() { 127 mUEventObserver.startObserving(UEVENT_FILTER); 128 scheduleUpdatePorts(); 129 } 130 131 public UsbPort[] getPorts() { 132 synchronized (mLock) { 133 final int count = mPorts.size(); 134 final UsbPort[] result = new UsbPort[count]; 135 for (int i = 0; i < count; i++) { 136 result[i] = mPorts.valueAt(i).mUsbPort; 137 } 138 return result; 139 } 140 } 141 142 public UsbPortStatus getPortStatus(String portId) { 143 synchronized (mLock) { 144 final PortInfo portInfo = mPorts.get(portId); 145 return portInfo != null ? portInfo.mUsbPortStatus : null; 146 } 147 } 148 149 public void setPortRoles(String portId, int newPowerRole, int newDataRole, 150 IndentingPrintWriter pw) { 151 synchronized (mLock) { 152 final PortInfo portInfo = mPorts.get(portId); 153 if (portInfo == null) { 154 if (pw != null) { 155 pw.println("No such USB port: " + portId); 156 } 157 return; 158 } 159 160 // Check whether the new role is actually supported. 161 if (!portInfo.mUsbPortStatus.isRoleCombinationSupported(newPowerRole, newDataRole)) { 162 logAndPrint(Log.ERROR, pw, "Attempted to set USB port into unsupported " 163 + "role combination: portId=" + portId 164 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) 165 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 166 return; 167 } 168 169 // Check whether anything actually changed. 170 final int currentDataRole = portInfo.mUsbPortStatus.getCurrentDataRole(); 171 final int currentPowerRole = portInfo.mUsbPortStatus.getCurrentPowerRole(); 172 if (currentDataRole == newDataRole && currentPowerRole == newPowerRole) { 173 if (pw != null) { 174 pw.println("No change."); 175 } 176 return; 177 } 178 179 // Determine whether we need to change the mode in order to accomplish this goal. 180 // We prefer not to do this since it's more likely to fail. 181 // 182 // Note: Arguably it might be worth allowing the client to influence this policy 183 // decision so that we could show more powerful developer facing UI but let's 184 // see how far we can get without having to do that. 185 final boolean canChangeMode = portInfo.mCanChangeMode; 186 final boolean canChangePowerRole = portInfo.mCanChangePowerRole; 187 final boolean canChangeDataRole = portInfo.mCanChangeDataRole; 188 final int currentMode = portInfo.mUsbPortStatus.getCurrentMode(); 189 final int newMode; 190 if ((!canChangePowerRole && currentPowerRole != newPowerRole) 191 || (!canChangeDataRole && currentDataRole != newDataRole)) { 192 if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE 193 && newDataRole == UsbPort.DATA_ROLE_HOST) { 194 newMode = UsbPort.MODE_DFP; 195 } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK 196 && newDataRole == UsbPort.DATA_ROLE_DEVICE) { 197 newMode = UsbPort.MODE_UFP; 198 } else { 199 logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations " 200 + "while attempting to change role: " + portInfo 201 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) 202 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 203 return; 204 } 205 } else { 206 newMode = currentMode; 207 } 208 209 // Make it happen. 210 logAndPrint(Log.INFO, pw, "Setting USB port mode and role: portId=" + portId 211 + ", currentMode=" + UsbPort.modeToString(currentMode) 212 + ", currentPowerRole=" + UsbPort.powerRoleToString(currentPowerRole) 213 + ", currentDataRole=" + UsbPort.dataRoleToString(currentDataRole) 214 + ", newMode=" + UsbPort.modeToString(newMode) 215 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) 216 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 217 218 SimulatedPortInfo sim = mSimulatedPorts.get(portId); 219 if (sim != null) { 220 // Change simulated state. 221 sim.mCurrentMode = newMode; 222 sim.mCurrentPowerRole = newPowerRole; 223 sim.mCurrentDataRole = newDataRole; 224 } else if (mHaveKernelSupport) { 225 // Change actual state. 226 final File portDir = new File(SYSFS_CLASS, portId); 227 if (!portDir.exists()) { 228 logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId); 229 return; 230 } 231 232 if (currentMode != newMode) { 233 // Changing the mode will have the side-effect of also changing 234 // the power and data roles but it might take some time to apply 235 // and the renegotiation might fail. Due to limitations of the USB 236 // hardware, we have no way of knowing whether it will work apriori 237 // which is why we would prefer to set the power and data roles 238 // directly instead. 239 if (!writeFile(portDir, SYSFS_PORT_MODE, 240 newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) { 241 logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: " 242 + "portId=" + portId 243 + ", newMode=" + UsbPort.modeToString(newMode)); 244 return; 245 } 246 } else { 247 // Change power and data role independently as needed. 248 if (currentPowerRole != newPowerRole) { 249 if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE, 250 newPowerRole == UsbPort.POWER_ROLE_SOURCE 251 ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) { 252 logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: " 253 + "portId=" + portId 254 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)); 255 return; 256 } 257 } 258 if (currentDataRole != newDataRole) { 259 if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE, 260 newDataRole == UsbPort.DATA_ROLE_HOST 261 ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) { 262 logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: " 263 + "portId=" + portId 264 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 265 return; 266 } 267 } 268 } 269 } 270 updatePortsLocked(pw); 271 } 272 } 273 274 public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) { 275 synchronized (mLock) { 276 if (mSimulatedPorts.containsKey(portId)) { 277 pw.println("Port with same name already exists. Please remove it first."); 278 return; 279 } 280 281 pw.println("Adding simulated port: portId=" + portId 282 + ", supportedModes=" + UsbPort.modeToString(supportedModes)); 283 mSimulatedPorts.put(portId, 284 new SimulatedPortInfo(portId, supportedModes)); 285 updatePortsLocked(pw); 286 } 287 } 288 289 public void connectSimulatedPort(String portId, int mode, boolean canChangeMode, 290 int powerRole, boolean canChangePowerRole, 291 int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) { 292 synchronized (mLock) { 293 final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId); 294 if (portInfo == null) { 295 pw.println("Cannot connect simulated port which does not exist."); 296 return; 297 } 298 299 if (mode == 0 || powerRole == 0 || dataRole == 0) { 300 pw.println("Cannot connect simulated port in null mode, " 301 + "power role, or data role."); 302 return; 303 } 304 305 if ((portInfo.mSupportedModes & mode) == 0) { 306 pw.println("Simulated port does not support mode: " + UsbPort.modeToString(mode)); 307 return; 308 } 309 310 pw.println("Connecting simulated port: portId=" + portId 311 + ", mode=" + UsbPort.modeToString(mode) 312 + ", canChangeMode=" + canChangeMode 313 + ", powerRole=" + UsbPort.powerRoleToString(powerRole) 314 + ", canChangePowerRole=" + canChangePowerRole 315 + ", dataRole=" + UsbPort.dataRoleToString(dataRole) 316 + ", canChangeDataRole=" + canChangeDataRole); 317 portInfo.mCurrentMode = mode; 318 portInfo.mCanChangeMode = canChangeMode; 319 portInfo.mCurrentPowerRole = powerRole; 320 portInfo.mCanChangePowerRole = canChangePowerRole; 321 portInfo.mCurrentDataRole = dataRole; 322 portInfo.mCanChangeDataRole = canChangeDataRole; 323 updatePortsLocked(pw); 324 } 325 } 326 327 public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) { 328 synchronized (mLock) { 329 final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId); 330 if (portInfo == null) { 331 pw.println("Cannot disconnect simulated port which does not exist."); 332 return; 333 } 334 335 pw.println("Disconnecting simulated port: portId=" + portId); 336 portInfo.mCurrentMode = 0; 337 portInfo.mCanChangeMode = false; 338 portInfo.mCurrentPowerRole = 0; 339 portInfo.mCanChangePowerRole = false; 340 portInfo.mCurrentDataRole = 0; 341 portInfo.mCanChangeDataRole = false; 342 updatePortsLocked(pw); 343 } 344 } 345 346 public void removeSimulatedPort(String portId, IndentingPrintWriter pw) { 347 synchronized (mLock) { 348 final int index = mSimulatedPorts.indexOfKey(portId); 349 if (index < 0) { 350 pw.println("Cannot remove simulated port which does not exist."); 351 return; 352 } 353 354 pw.println("Disconnecting simulated port: portId=" + portId); 355 mSimulatedPorts.removeAt(index); 356 updatePortsLocked(pw); 357 } 358 } 359 360 public void resetSimulation(IndentingPrintWriter pw) { 361 synchronized (mLock) { 362 pw.println("Removing all simulated ports and ending simulation."); 363 if (!mSimulatedPorts.isEmpty()) { 364 mSimulatedPorts.clear(); 365 updatePortsLocked(pw); 366 } 367 } 368 } 369 370 public void dump(IndentingPrintWriter pw) { 371 synchronized (mLock) { 372 pw.print("USB Port State:"); 373 if (!mSimulatedPorts.isEmpty()) { 374 pw.print(" (simulation active; end with 'dumpsys usb reset')"); 375 } 376 pw.println(); 377 378 if (mPorts.isEmpty()) { 379 pw.println(" <no ports>"); 380 } else { 381 for (PortInfo portInfo : mPorts.values()) { 382 pw.println(" " + portInfo.mUsbPort.getId() + ": " + portInfo); 383 } 384 } 385 } 386 } 387 388 private void updatePortsLocked(IndentingPrintWriter pw) { 389 // Assume all ports are gone unless informed otherwise. 390 // Kind of pessimistic but simple. 391 for (int i = mPorts.size(); i-- > 0; ) { 392 mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED; 393 } 394 395 // Enumerate all extant ports. 396 if (!mSimulatedPorts.isEmpty()) { 397 final int count = mSimulatedPorts.size(); 398 for (int i = 0; i < count; i++) { 399 final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i); 400 addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes, 401 portInfo.mCurrentMode, portInfo.mCanChangeMode, 402 portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole, 403 portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw); 404 } 405 } else if (mHaveKernelSupport) { 406 final File[] portDirs = new File(SYSFS_CLASS).listFiles(); 407 if (portDirs != null) { 408 for (File portDir : portDirs) { 409 if (!portDir.isDirectory()) { 410 continue; 411 } 412 413 // Parse the sysfs file contents. 414 final String portId = portDir.getName(); 415 final int supportedModes = readSupportedModes(portDir); 416 final int currentMode = readCurrentMode(portDir); 417 final boolean canChangeMode = canChangeMode(portDir); 418 final int currentPowerRole = readCurrentPowerRole(portDir); 419 final boolean canChangePowerRole = canChangePowerRole(portDir); 420 final int currentDataRole = readCurrentDataRole(portDir); 421 final boolean canChangeDataRole = canChangeDataRole(portDir); 422 addOrUpdatePortLocked(portId, supportedModes, 423 currentMode, canChangeMode, 424 currentPowerRole, canChangePowerRole, 425 currentDataRole, canChangeDataRole, pw); 426 } 427 } 428 } 429 430 // Process the updates. 431 // Once finished, the list of ports will only contain ports in DISPOSITION_READY. 432 for (int i = mPorts.size(); i-- > 0; ) { 433 final PortInfo portInfo = mPorts.valueAt(i); 434 switch (portInfo.mDisposition) { 435 case PortInfo.DISPOSITION_ADDED: 436 handlePortAddedLocked(portInfo, pw); 437 portInfo.mDisposition = PortInfo.DISPOSITION_READY; 438 break; 439 case PortInfo.DISPOSITION_CHANGED: 440 handlePortChangedLocked(portInfo, pw); 441 portInfo.mDisposition = PortInfo.DISPOSITION_READY; 442 break; 443 case PortInfo.DISPOSITION_REMOVED: 444 mPorts.removeAt(i); 445 portInfo.mUsbPortStatus = null; // must do this early 446 handlePortRemovedLocked(portInfo, pw); 447 break; 448 } 449 } 450 } 451 452 // Must only be called by updatePortsLocked. 453 private void addOrUpdatePortLocked(String portId, int supportedModes, 454 int currentMode, boolean canChangeMode, 455 int currentPowerRole, boolean canChangePowerRole, 456 int currentDataRole, boolean canChangeDataRole, 457 IndentingPrintWriter pw) { 458 // Only allow mode switch capability for dual role ports. 459 // Validate that the current mode matches the supported modes we expect. 460 if (supportedModes != UsbPort.MODE_DUAL) { 461 canChangeMode = false; 462 if (currentMode != 0 && currentMode != supportedModes) { 463 logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB " 464 + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes) 465 + ", currentMode=" + UsbPort.modeToString(currentMode)); 466 currentMode = 0; 467 } 468 } 469 470 // Determine the supported role combinations. 471 // Note that the policy is designed to prefer setting the power and data 472 // role independently rather than changing the mode. 473 int supportedRoleCombinations = UsbPort.combineRolesAsBit( 474 currentPowerRole, currentDataRole); 475 if (currentMode != 0 && currentPowerRole != 0 && currentDataRole != 0) { 476 if (canChangePowerRole && canChangeDataRole) { 477 // Can change both power and data role independently. 478 // Assume all combinations are possible. 479 supportedRoleCombinations |= 480 COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE 481 | COMBO_SINK_HOST | COMBO_SINK_DEVICE; 482 } else if (canChangePowerRole) { 483 // Can only change power role. 484 // Assume data role must remain at its current value. 485 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 486 UsbPort.POWER_ROLE_SOURCE, currentDataRole); 487 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 488 UsbPort.POWER_ROLE_SINK, currentDataRole); 489 } else if (canChangeDataRole) { 490 // Can only change data role. 491 // Assume power role must remain at its current value. 492 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 493 currentPowerRole, UsbPort.DATA_ROLE_HOST); 494 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 495 currentPowerRole, UsbPort.DATA_ROLE_DEVICE); 496 } else if (canChangeMode) { 497 // Can only change the mode. 498 // Assume both standard UFP and DFP configurations will become available 499 // when this happens. 500 supportedRoleCombinations |= COMBO_SOURCE_HOST | COMBO_SINK_DEVICE; 501 } 502 } 503 504 // Update the port data structures. 505 PortInfo portInfo = mPorts.get(portId); 506 if (portInfo == null) { 507 portInfo = new PortInfo(portId, supportedModes); 508 portInfo.setStatus(currentMode, canChangeMode, 509 currentPowerRole, canChangePowerRole, 510 currentDataRole, canChangeDataRole, 511 supportedRoleCombinations); 512 mPorts.put(portId, portInfo); 513 } else { 514 // Sanity check that ports aren't changing definition out from under us. 515 if (supportedModes != portInfo.mUsbPort.getSupportedModes()) { 516 logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from " 517 + "USB port driver (should be immutable): " 518 + "previous=" + UsbPort.modeToString( 519 portInfo.mUsbPort.getSupportedModes()) 520 + ", current=" + UsbPort.modeToString(supportedModes)); 521 } 522 523 if (portInfo.setStatus(currentMode, canChangeMode, 524 currentPowerRole, canChangePowerRole, 525 currentDataRole, canChangeDataRole, 526 supportedRoleCombinations)) { 527 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; 528 } else { 529 portInfo.mDisposition = PortInfo.DISPOSITION_READY; 530 } 531 } 532 } 533 534 private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) { 535 logAndPrint(Log.INFO, pw, "USB port added: " + portInfo); 536 sendPortChangedBroadcastLocked(portInfo); 537 } 538 539 private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) { 540 logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo); 541 sendPortChangedBroadcastLocked(portInfo); 542 } 543 544 private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) { 545 logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo); 546 sendPortChangedBroadcastLocked(portInfo); 547 } 548 549 private void sendPortChangedBroadcastLocked(PortInfo portInfo) { 550 final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED); 551 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 552 intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort); 553 intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus); 554 555 // Guard against possible reentrance by posting the broadcast from the handler 556 // instead of from within the critical section. 557 mHandler.post(new Runnable() { 558 @Override 559 public void run() { 560 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 561 } 562 }); 563 } 564 565 private void scheduleUpdatePorts() { 566 if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) { 567 mHandler.sendEmptyMessage(MSG_UPDATE_PORTS); 568 } 569 } 570 571 private static int readSupportedModes(File portDir) { 572 int modes = 0; 573 final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES); 574 if (contents != null) { 575 if (contents.contains(PORT_MODE_DFP)) { 576 modes |= UsbPort.MODE_DFP; 577 } 578 if (contents.contains(PORT_MODE_UFP)) { 579 modes |= UsbPort.MODE_UFP; 580 } 581 } 582 return modes; 583 } 584 585 private static int readCurrentMode(File portDir) { 586 final String contents = readFile(portDir, SYSFS_PORT_MODE); 587 if (contents != null) { 588 if (contents.equals(PORT_MODE_DFP)) { 589 return UsbPort.MODE_DFP; 590 } 591 if (contents.equals(PORT_MODE_UFP)) { 592 return UsbPort.MODE_UFP; 593 } 594 } 595 return 0; 596 } 597 598 private static int readCurrentPowerRole(File portDir) { 599 final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE); 600 if (contents != null) { 601 if (contents.equals(PORT_POWER_ROLE_SOURCE)) { 602 return UsbPort.POWER_ROLE_SOURCE; 603 } 604 if (contents.equals(PORT_POWER_ROLE_SINK)) { 605 return UsbPort.POWER_ROLE_SINK; 606 } 607 } 608 return 0; 609 } 610 611 private static int readCurrentDataRole(File portDir) { 612 final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE); 613 if (contents != null) { 614 if (contents.equals(PORT_DATA_ROLE_HOST)) { 615 return UsbPort.DATA_ROLE_HOST; 616 } 617 if (contents.equals(PORT_DATA_ROLE_DEVICE)) { 618 return UsbPort.DATA_ROLE_DEVICE; 619 } 620 } 621 return 0; 622 } 623 624 private static boolean canChangeMode(File portDir) { 625 return new File(portDir, SYSFS_PORT_MODE).canWrite(); 626 } 627 628 private static boolean canChangePowerRole(File portDir) { 629 return new File(portDir, SYSFS_PORT_POWER_ROLE).canWrite(); 630 } 631 632 private static boolean canChangeDataRole(File portDir) { 633 return new File(portDir, SYSFS_PORT_DATA_ROLE).canWrite(); 634 } 635 636 private static String readFile(File dir, String filename) { 637 final File file = new File(dir, filename); 638 try { 639 return IoUtils.readFileAsString(file.getAbsolutePath()).trim(); 640 } catch (IOException ex) { 641 return null; 642 } 643 } 644 645 private static boolean writeFile(File dir, String filename, String contents) { 646 final File file = new File(dir, filename); 647 try { 648 try (FileWriter writer = new FileWriter(file)) { 649 writer.write(contents); 650 } 651 return true; 652 } catch (IOException ex) { 653 return false; 654 } 655 } 656 657 private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { 658 Slog.println(priority, TAG, msg); 659 if (pw != null) { 660 pw.println(msg); 661 } 662 } 663 664 private final Handler mHandler = new Handler(FgThread.get().getLooper()) { 665 @Override 666 public void handleMessage(Message msg) { 667 switch (msg.what) { 668 case MSG_UPDATE_PORTS: { 669 synchronized (mLock) { 670 updatePortsLocked(null); 671 } 672 break; 673 } 674 } 675 } 676 }; 677 678 private final UEventObserver mUEventObserver = new UEventObserver() { 679 @Override 680 public void onUEvent(UEvent event) { 681 scheduleUpdatePorts(); 682 } 683 }; 684 685 /** 686 * Describes a USB port. 687 */ 688 private static final class PortInfo { 689 public static final int DISPOSITION_ADDED = 0; 690 public static final int DISPOSITION_CHANGED = 1; 691 public static final int DISPOSITION_READY = 2; 692 public static final int DISPOSITION_REMOVED = 3; 693 694 public final UsbPort mUsbPort; 695 public UsbPortStatus mUsbPortStatus; 696 public boolean mCanChangeMode; 697 public boolean mCanChangePowerRole; 698 public boolean mCanChangeDataRole; 699 public int mDisposition; // default initialized to 0 which means added 700 701 public PortInfo(String portId, int supportedModes) { 702 mUsbPort = new UsbPort(portId, supportedModes); 703 } 704 705 public boolean setStatus(int currentMode, boolean canChangeMode, 706 int currentPowerRole, boolean canChangePowerRole, 707 int currentDataRole, boolean canChangeDataRole, 708 int supportedRoleCombinations) { 709 mCanChangeMode = canChangeMode; 710 mCanChangePowerRole = canChangePowerRole; 711 mCanChangeDataRole = canChangeDataRole; 712 if (mUsbPortStatus == null 713 || mUsbPortStatus.getCurrentMode() != currentMode 714 || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole 715 || mUsbPortStatus.getCurrentDataRole() != currentDataRole 716 || mUsbPortStatus.getSupportedRoleCombinations() 717 != supportedRoleCombinations) { 718 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, 719 supportedRoleCombinations); 720 return true; 721 } 722 return false; 723 } 724 725 @Override 726 public String toString() { 727 return "port=" + mUsbPort + ", status=" + mUsbPortStatus 728 + ", canChangeMode=" + mCanChangeMode 729 + ", canChangePowerRole=" + mCanChangePowerRole 730 + ", canChangeDataRole=" + mCanChangeDataRole; 731 } 732 } 733 734 /** 735 * Describes a simulated USB port. 736 * Roughly mirrors the information we would ordinarily get from the kernel. 737 */ 738 private static final class SimulatedPortInfo { 739 public final String mPortId; 740 public final int mSupportedModes; 741 public int mCurrentMode; 742 public boolean mCanChangeMode; 743 public int mCurrentPowerRole; 744 public boolean mCanChangePowerRole; 745 public int mCurrentDataRole; 746 public boolean mCanChangeDataRole; 747 748 public SimulatedPortInfo(String portId, int supportedModes) { 749 mPortId = portId; 750 mSupportedModes = supportedModes; 751 } 752 } 753 } 754