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 android.content.Context; 20 import android.content.Intent; 21 import android.hardware.usb.UsbManager; 22 import android.hardware.usb.UsbPort; 23 import android.hardware.usb.UsbPortStatus; 24 import android.hardware.usb.V1_0.IUsb; 25 import android.hardware.usb.V1_0.IUsbCallback; 26 import android.hardware.usb.V1_0.PortRole; 27 import android.hardware.usb.V1_0.PortRoleType; 28 import android.hardware.usb.V1_0.PortStatus; 29 import android.hardware.usb.V1_0.Status; 30 import android.hidl.manager.V1_0.IServiceManager; 31 import android.hidl.manager.V1_0.IServiceNotification; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.HwBinder; 35 import android.os.Message; 36 import android.os.Parcel; 37 import android.os.Parcelable; 38 import android.os.RemoteException; 39 import android.os.UserHandle; 40 import android.util.ArrayMap; 41 import android.util.Log; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.GuardedBy; 45 import com.android.internal.util.IndentingPrintWriter; 46 import com.android.server.FgThread; 47 48 import java.util.ArrayList; 49 import java.util.NoSuchElementException; 50 51 /** 52 * Allows trusted components to control the properties of physical USB ports 53 * via the IUsb.hal. 54 * <p> 55 * Note: This interface may not be supported on all chipsets since the USB drivers 56 * must be changed to publish this information through the module. At the moment 57 * we only need this for devices with USB Type C ports to allow the System UI to 58 * control USB charging and data direction. On devices that do not support this 59 * interface the list of ports may incorrectly appear to be empty 60 * (but we don't care today). 61 * </p> 62 */ 63 public class UsbPortManager { 64 private static final String TAG = "UsbPortManager"; 65 66 private static final int MSG_UPDATE_PORTS = 1; 67 68 // All non-trivial role combinations. 69 private static final int COMBO_SOURCE_HOST = 70 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST); 71 private static final int COMBO_SOURCE_DEVICE = 72 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE); 73 private static final int COMBO_SINK_HOST = 74 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST); 75 private static final int COMBO_SINK_DEVICE = 76 UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE); 77 78 // The system context. 79 private final Context mContext; 80 81 // Proxy object for the usb hal daemon. 82 @GuardedBy("mLock") 83 private IUsb mProxy = null; 84 85 // Callback when the UsbPort status is changed by the kernel. 86 // Mostly due a command sent by the remote Usb device. 87 private HALCallback mHALCallback = new HALCallback(null, this); 88 89 // Notification object used to listen to the start of the usb daemon. 90 private final ServiceNotification mServiceNotification = new ServiceNotification(); 91 92 // Cookie sent for usb hal death notification. 93 private static final int USB_HAL_DEATH_COOKIE = 1000; 94 95 // Used as the key while sending the bundle to Main thread. 96 private static final String PORT_INFO = "port_info"; 97 98 // This is monitored to prevent updating the protInfo before the system 99 // is ready. 100 private boolean mSystemReady; 101 102 // Mutex for all mutable shared state. 103 private final Object mLock = new Object(); 104 105 // List of all ports, indexed by id. 106 // Ports may temporarily have different dispositions as they are added or removed 107 // but the class invariant is that this list will only contain ports with DISPOSITION_READY 108 // except while updatePortsLocked() is in progress. 109 private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>(); 110 111 // List of all simulated ports, indexed by id. 112 private final ArrayMap<String, RawPortInfo> mSimulatedPorts = 113 new ArrayMap<String, RawPortInfo>(); 114 115 public UsbPortManager(Context context) { 116 mContext = context; 117 try { 118 boolean ret = IServiceManager.getService() 119 .registerForNotifications("android.hardware.usb (at) 1.0::IUsb", 120 "", mServiceNotification); 121 if (!ret) { 122 logAndPrint(Log.ERROR, null, 123 "Failed to register service start notification"); 124 } 125 } catch (RemoteException e) { 126 logAndPrintException(null, 127 "Failed to register service start notification", e); 128 return; 129 } 130 connectToProxy(null); 131 } 132 133 public void systemReady() { 134 if (mProxy != null) { 135 try { 136 mProxy.queryPortStatus(); 137 } catch (RemoteException e) { 138 logAndPrintException(null, 139 "ServiceStart: Failed to query port status", e); 140 } 141 } 142 mSystemReady = true; 143 } 144 145 public UsbPort[] getPorts() { 146 synchronized (mLock) { 147 final int count = mPorts.size(); 148 final UsbPort[] result = new UsbPort[count]; 149 for (int i = 0; i < count; i++) { 150 result[i] = mPorts.valueAt(i).mUsbPort; 151 } 152 return result; 153 } 154 } 155 156 public UsbPortStatus getPortStatus(String portId) { 157 synchronized (mLock) { 158 final PortInfo portInfo = mPorts.get(portId); 159 return portInfo != null ? portInfo.mUsbPortStatus : null; 160 } 161 } 162 163 public void setPortRoles(String portId, int newPowerRole, int newDataRole, 164 IndentingPrintWriter pw) { 165 synchronized (mLock) { 166 final PortInfo portInfo = mPorts.get(portId); 167 if (portInfo == null) { 168 if (pw != null) { 169 pw.println("No such USB port: " + portId); 170 } 171 return; 172 } 173 174 // Check whether the new role is actually supported. 175 if (!portInfo.mUsbPortStatus.isRoleCombinationSupported(newPowerRole, newDataRole)) { 176 logAndPrint(Log.ERROR, pw, "Attempted to set USB port into unsupported " 177 + "role combination: portId=" + portId 178 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) 179 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 180 return; 181 } 182 183 // Check whether anything actually changed. 184 final int currentDataRole = portInfo.mUsbPortStatus.getCurrentDataRole(); 185 final int currentPowerRole = portInfo.mUsbPortStatus.getCurrentPowerRole(); 186 if (currentDataRole == newDataRole && currentPowerRole == newPowerRole) { 187 if (pw != null) { 188 pw.println("No change."); 189 } 190 return; 191 } 192 193 // Determine whether we need to change the mode in order to accomplish this goal. 194 // We prefer not to do this since it's more likely to fail. 195 // 196 // Note: Arguably it might be worth allowing the client to influence this policy 197 // decision so that we could show more powerful developer facing UI but let's 198 // see how far we can get without having to do that. 199 final boolean canChangeMode = portInfo.mCanChangeMode; 200 final boolean canChangePowerRole = portInfo.mCanChangePowerRole; 201 final boolean canChangeDataRole = portInfo.mCanChangeDataRole; 202 final int currentMode = portInfo.mUsbPortStatus.getCurrentMode(); 203 final int newMode; 204 if ((!canChangePowerRole && currentPowerRole != newPowerRole) 205 || (!canChangeDataRole && currentDataRole != newDataRole)) { 206 if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE 207 && newDataRole == UsbPort.DATA_ROLE_HOST) { 208 newMode = UsbPort.MODE_DFP; 209 } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK 210 && newDataRole == UsbPort.DATA_ROLE_DEVICE) { 211 newMode = UsbPort.MODE_UFP; 212 } else { 213 logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations " 214 + "while attempting to change role: " + portInfo 215 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) 216 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 217 return; 218 } 219 } else { 220 newMode = currentMode; 221 } 222 223 // Make it happen. 224 logAndPrint(Log.INFO, pw, "Setting USB port mode and role: portId=" + portId 225 + ", currentMode=" + UsbPort.modeToString(currentMode) 226 + ", currentPowerRole=" + UsbPort.powerRoleToString(currentPowerRole) 227 + ", currentDataRole=" + UsbPort.dataRoleToString(currentDataRole) 228 + ", newMode=" + UsbPort.modeToString(newMode) 229 + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole) 230 + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole)); 231 232 RawPortInfo sim = mSimulatedPorts.get(portId); 233 if (sim != null) { 234 // Change simulated state. 235 sim.currentMode = newMode; 236 sim.currentPowerRole = newPowerRole; 237 sim.currentDataRole = newDataRole; 238 updatePortsLocked(pw, null); 239 } else if (mProxy != null) { 240 if (currentMode != newMode) { 241 // Changing the mode will have the side-effect of also changing 242 // the power and data roles but it might take some time to apply 243 // and the renegotiation might fail. Due to limitations of the USB 244 // hardware, we have no way of knowing whether it will work apriori 245 // which is why we would prefer to set the power and data roles 246 // directly instead. 247 248 logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: " 249 + "portId=" + portId 250 + ", newMode=" + UsbPort.modeToString(newMode)); 251 PortRole newRole = new PortRole(); 252 newRole.type = PortRoleType.MODE; 253 newRole.role = newMode; 254 try { 255 mProxy.switchRole(portId, newRole); 256 } catch (RemoteException e) { 257 logAndPrintException(pw, "Failed to set the USB port mode: " 258 + "portId=" + portId 259 + ", newMode=" + UsbPort.modeToString(newRole.role), e); 260 return; 261 } 262 } else { 263 // Change power and data role independently as needed. 264 if (currentPowerRole != newPowerRole) { 265 PortRole newRole = new PortRole(); 266 newRole.type = PortRoleType.POWER_ROLE; 267 newRole.role = newPowerRole; 268 try { 269 mProxy.switchRole(portId, newRole); 270 } catch (RemoteException e) { 271 logAndPrintException(pw, "Failed to set the USB port power role: " 272 + "portId=" + portId 273 + ", newPowerRole=" + UsbPort.powerRoleToString 274 (newRole.role), 275 e); 276 return; 277 } 278 } 279 if (currentDataRole != newDataRole) { 280 PortRole newRole = new PortRole(); 281 newRole.type = PortRoleType.DATA_ROLE; 282 newRole.role = newDataRole; 283 try { 284 mProxy.switchRole(portId, newRole); 285 } catch (RemoteException e) { 286 logAndPrintException(pw, "Failed to set the USB port data role: " 287 + "portId=" + portId 288 + ", newDataRole=" + UsbPort.dataRoleToString(newRole 289 .role), 290 e); 291 return; 292 } 293 } 294 } 295 } 296 } 297 } 298 299 public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) { 300 synchronized (mLock) { 301 if (mSimulatedPorts.containsKey(portId)) { 302 pw.println("Port with same name already exists. Please remove it first."); 303 return; 304 } 305 306 pw.println("Adding simulated port: portId=" + portId 307 + ", supportedModes=" + UsbPort.modeToString(supportedModes)); 308 mSimulatedPorts.put(portId, 309 new RawPortInfo(portId, supportedModes)); 310 updatePortsLocked(pw, null); 311 } 312 } 313 314 public void connectSimulatedPort(String portId, int mode, boolean canChangeMode, 315 int powerRole, boolean canChangePowerRole, 316 int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) { 317 synchronized (mLock) { 318 final RawPortInfo portInfo = mSimulatedPorts.get(portId); 319 if (portInfo == null) { 320 pw.println("Cannot connect simulated port which does not exist."); 321 return; 322 } 323 324 if (mode == 0 || powerRole == 0 || dataRole == 0) { 325 pw.println("Cannot connect simulated port in null mode, " 326 + "power role, or data role."); 327 return; 328 } 329 330 if ((portInfo.supportedModes & mode) == 0) { 331 pw.println("Simulated port does not support mode: " + UsbPort.modeToString(mode)); 332 return; 333 } 334 335 pw.println("Connecting simulated port: portId=" + portId 336 + ", mode=" + UsbPort.modeToString(mode) 337 + ", canChangeMode=" + canChangeMode 338 + ", powerRole=" + UsbPort.powerRoleToString(powerRole) 339 + ", canChangePowerRole=" + canChangePowerRole 340 + ", dataRole=" + UsbPort.dataRoleToString(dataRole) 341 + ", canChangeDataRole=" + canChangeDataRole); 342 portInfo.currentMode = mode; 343 portInfo.canChangeMode = canChangeMode; 344 portInfo.currentPowerRole = powerRole; 345 portInfo.canChangePowerRole = canChangePowerRole; 346 portInfo.currentDataRole = dataRole; 347 portInfo.canChangeDataRole = canChangeDataRole; 348 updatePortsLocked(pw, null); 349 } 350 } 351 352 public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) { 353 synchronized (mLock) { 354 final RawPortInfo portInfo = mSimulatedPorts.get(portId); 355 if (portInfo == null) { 356 pw.println("Cannot disconnect simulated port which does not exist."); 357 return; 358 } 359 360 pw.println("Disconnecting simulated port: portId=" + portId); 361 portInfo.currentMode = 0; 362 portInfo.canChangeMode = false; 363 portInfo.currentPowerRole = 0; 364 portInfo.canChangePowerRole = false; 365 portInfo.currentDataRole = 0; 366 portInfo.canChangeDataRole = false; 367 updatePortsLocked(pw, null); 368 } 369 } 370 371 public void removeSimulatedPort(String portId, IndentingPrintWriter pw) { 372 synchronized (mLock) { 373 final int index = mSimulatedPorts.indexOfKey(portId); 374 if (index < 0) { 375 pw.println("Cannot remove simulated port which does not exist."); 376 return; 377 } 378 379 pw.println("Disconnecting simulated port: portId=" + portId); 380 mSimulatedPorts.removeAt(index); 381 updatePortsLocked(pw, null); 382 } 383 } 384 385 public void resetSimulation(IndentingPrintWriter pw) { 386 synchronized (mLock) { 387 pw.println("Removing all simulated ports and ending simulation."); 388 if (!mSimulatedPorts.isEmpty()) { 389 mSimulatedPorts.clear(); 390 updatePortsLocked(pw, null); 391 } 392 } 393 } 394 395 public void dump(IndentingPrintWriter pw) { 396 synchronized (mLock) { 397 pw.print("USB Port State:"); 398 if (!mSimulatedPorts.isEmpty()) { 399 pw.print(" (simulation active; end with 'dumpsys usb reset')"); 400 } 401 pw.println(); 402 403 if (mPorts.isEmpty()) { 404 pw.println(" <no ports>"); 405 } else { 406 for (PortInfo portInfo : mPorts.values()) { 407 pw.println(" " + portInfo.mUsbPort.getId() + ": " + portInfo); 408 } 409 } 410 } 411 } 412 413 private static class HALCallback extends IUsbCallback.Stub { 414 public IndentingPrintWriter pw; 415 public UsbPortManager portManager; 416 417 HALCallback() { 418 super(); 419 } 420 421 HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) { 422 this.pw = pw; 423 this.portManager = portManager; 424 } 425 426 public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) { 427 if (!portManager.mSystemReady) { 428 return; 429 } 430 431 if (retval != Status.SUCCESS) { 432 logAndPrint(Log.ERROR, pw, "port status enquiry failed"); 433 return; 434 } 435 436 ArrayList<RawPortInfo> newPortInfo = new ArrayList<RawPortInfo>(); 437 438 for (PortStatus current : currentPortStatus) { 439 RawPortInfo temp = new RawPortInfo(current.portName, 440 current.supportedModes, current.currentMode, 441 current.canChangeMode, current.currentPowerRole, 442 current.canChangePowerRole, 443 current.currentDataRole, current.canChangeDataRole); 444 newPortInfo.add(temp); 445 logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName); 446 } 447 448 Message message = portManager.mHandler.obtainMessage(); 449 Bundle bundle = new Bundle(); 450 bundle.putParcelableArrayList(PORT_INFO, newPortInfo); 451 message.what = MSG_UPDATE_PORTS; 452 message.setData(bundle); 453 portManager.mHandler.sendMessage(message); 454 return; 455 } 456 457 public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) { 458 if (retval == Status.SUCCESS) { 459 logAndPrint(Log.INFO, pw, portName + " role switch successful"); 460 } else { 461 logAndPrint(Log.ERROR, pw, portName + " role switch failed"); 462 } 463 } 464 }; 465 466 final class DeathRecipient implements HwBinder.DeathRecipient { 467 public IndentingPrintWriter pw; 468 469 DeathRecipient(IndentingPrintWriter pw) { 470 this.pw = pw; 471 } 472 473 @Override 474 public void serviceDied(long cookie) { 475 if (cookie == USB_HAL_DEATH_COOKIE) { 476 logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie); 477 synchronized (mLock) { 478 mProxy = null; 479 } 480 } 481 } 482 } 483 484 final class ServiceNotification extends IServiceNotification.Stub { 485 @Override 486 public void onRegistration(String fqName, String name, boolean preexisting) { 487 logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name); 488 connectToProxy(null); 489 } 490 } 491 492 private void connectToProxy(IndentingPrintWriter pw) { 493 synchronized (mLock) { 494 if (mProxy != null) { 495 return; 496 } 497 498 try { 499 mProxy = IUsb.getService(); 500 mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE); 501 mProxy.setCallback(mHALCallback); 502 mProxy.queryPortStatus(); 503 } catch (NoSuchElementException e) { 504 logAndPrintException(pw, "connectToProxy: usb hal service not found." 505 + " Did the service fail to start?", e); 506 } catch (RemoteException e) { 507 logAndPrintException(pw, "connectToProxy: usb hal service not responding", e); 508 } 509 } 510 } 511 512 /** 513 * Simulated ports directly add the new roles to mSimulatedPorts before calling. 514 * USB hal callback populates and sends the newPortInfo. 515 */ 516 private void updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo) { 517 for (int i = mPorts.size(); i-- > 0; ) { 518 mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED; 519 } 520 521 // Enumerate all extant ports. 522 if (!mSimulatedPorts.isEmpty()) { 523 final int count = mSimulatedPorts.size(); 524 for (int i = 0; i < count; i++) { 525 final RawPortInfo portInfo = mSimulatedPorts.valueAt(i); 526 addOrUpdatePortLocked(portInfo.portId, portInfo.supportedModes, 527 portInfo.currentMode, portInfo.canChangeMode, 528 portInfo.currentPowerRole, portInfo.canChangePowerRole, 529 portInfo.currentDataRole, portInfo.canChangeDataRole, pw); 530 } 531 } else { 532 for (RawPortInfo currentPortInfo : newPortInfo) { 533 addOrUpdatePortLocked(currentPortInfo.portId, currentPortInfo.supportedModes, 534 currentPortInfo.currentMode, currentPortInfo.canChangeMode, 535 currentPortInfo.currentPowerRole, currentPortInfo.canChangePowerRole, 536 currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole, pw); 537 } 538 } 539 540 // Process the updates. 541 // Once finished, the list of ports will only contain ports in DISPOSITION_READY. 542 for (int i = mPorts.size(); i-- > 0; ) { 543 final PortInfo portInfo = mPorts.valueAt(i); 544 switch (portInfo.mDisposition) { 545 case PortInfo.DISPOSITION_ADDED: 546 handlePortAddedLocked(portInfo, pw); 547 portInfo.mDisposition = PortInfo.DISPOSITION_READY; 548 break; 549 case PortInfo.DISPOSITION_CHANGED: 550 handlePortChangedLocked(portInfo, pw); 551 portInfo.mDisposition = PortInfo.DISPOSITION_READY; 552 break; 553 case PortInfo.DISPOSITION_REMOVED: 554 mPorts.removeAt(i); 555 portInfo.mUsbPortStatus = null; // must do this early 556 handlePortRemovedLocked(portInfo, pw); 557 break; 558 } 559 } 560 } 561 562 563 // Must only be called by updatePortsLocked. 564 private void addOrUpdatePortLocked(String portId, int supportedModes, 565 int currentMode, boolean canChangeMode, 566 int currentPowerRole, boolean canChangePowerRole, 567 int currentDataRole, boolean canChangeDataRole, 568 IndentingPrintWriter pw) { 569 // Only allow mode switch capability for dual role ports. 570 // Validate that the current mode matches the supported modes we expect. 571 if (supportedModes != UsbPort.MODE_DUAL) { 572 canChangeMode = false; 573 if (currentMode != 0 && currentMode != supportedModes) { 574 logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB " 575 + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes) 576 + ", currentMode=" + UsbPort.modeToString(currentMode)); 577 currentMode = 0; 578 } 579 } 580 581 // Determine the supported role combinations. 582 // Note that the policy is designed to prefer setting the power and data 583 // role independently rather than changing the mode. 584 int supportedRoleCombinations = UsbPort.combineRolesAsBit( 585 currentPowerRole, currentDataRole); 586 if (currentMode != 0 && currentPowerRole != 0 && currentDataRole != 0) { 587 if (canChangePowerRole && canChangeDataRole) { 588 // Can change both power and data role independently. 589 // Assume all combinations are possible. 590 supportedRoleCombinations |= 591 COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE 592 | COMBO_SINK_HOST | COMBO_SINK_DEVICE; 593 } else if (canChangePowerRole) { 594 // Can only change power role. 595 // Assume data role must remain at its current value. 596 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 597 UsbPort.POWER_ROLE_SOURCE, currentDataRole); 598 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 599 UsbPort.POWER_ROLE_SINK, currentDataRole); 600 } else if (canChangeDataRole) { 601 // Can only change data role. 602 // Assume power role must remain at its current value. 603 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 604 currentPowerRole, UsbPort.DATA_ROLE_HOST); 605 supportedRoleCombinations |= UsbPort.combineRolesAsBit( 606 currentPowerRole, UsbPort.DATA_ROLE_DEVICE); 607 } else if (canChangeMode) { 608 // Can only change the mode. 609 // Assume both standard UFP and DFP configurations will become available 610 // when this happens. 611 supportedRoleCombinations |= COMBO_SOURCE_HOST | COMBO_SINK_DEVICE; 612 } 613 } 614 615 // Update the port data structures. 616 PortInfo portInfo = mPorts.get(portId); 617 if (portInfo == null) { 618 portInfo = new PortInfo(portId, supportedModes); 619 portInfo.setStatus(currentMode, canChangeMode, 620 currentPowerRole, canChangePowerRole, 621 currentDataRole, canChangeDataRole, 622 supportedRoleCombinations); 623 mPorts.put(portId, portInfo); 624 } else { 625 // Sanity check that ports aren't changing definition out from under us. 626 if (supportedModes != portInfo.mUsbPort.getSupportedModes()) { 627 logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from " 628 + "USB port driver (should be immutable): " 629 + "previous=" + UsbPort.modeToString( 630 portInfo.mUsbPort.getSupportedModes()) 631 + ", current=" + UsbPort.modeToString(supportedModes)); 632 } 633 634 if (portInfo.setStatus(currentMode, canChangeMode, 635 currentPowerRole, canChangePowerRole, 636 currentDataRole, canChangeDataRole, 637 supportedRoleCombinations)) { 638 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED; 639 } else { 640 portInfo.mDisposition = PortInfo.DISPOSITION_READY; 641 } 642 } 643 } 644 645 private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) { 646 logAndPrint(Log.INFO, pw, "USB port added: " + portInfo); 647 sendPortChangedBroadcastLocked(portInfo); 648 } 649 650 private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) { 651 logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo); 652 sendPortChangedBroadcastLocked(portInfo); 653 } 654 655 private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) { 656 logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo); 657 sendPortChangedBroadcastLocked(portInfo); 658 } 659 660 private void sendPortChangedBroadcastLocked(PortInfo portInfo) { 661 final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED); 662 intent.addFlags( 663 Intent.FLAG_RECEIVER_FOREGROUND | 664 Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 665 intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort); 666 intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus); 667 668 // Guard against possible reentrance by posting the broadcast from the handler 669 // instead of from within the critical section. 670 mHandler.post(new Runnable() { 671 @Override 672 public void run() { 673 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 674 } 675 }); 676 } 677 678 private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) { 679 Slog.println(priority, TAG, msg); 680 if (pw != null) { 681 pw.println(msg); 682 } 683 } 684 685 private static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) { 686 Slog.e(TAG, msg, e); 687 if (pw != null) { 688 pw.println(msg + e); 689 } 690 } 691 692 private final Handler mHandler = new Handler(FgThread.get().getLooper()) { 693 @Override 694 public void handleMessage(Message msg) { 695 switch (msg.what) { 696 case MSG_UPDATE_PORTS: { 697 Bundle b = msg.getData(); 698 ArrayList<RawPortInfo> PortInfo = b.getParcelableArrayList(PORT_INFO); 699 synchronized (mLock) { 700 updatePortsLocked(null, PortInfo); 701 } 702 break; 703 } 704 } 705 } 706 }; 707 708 /** 709 * Describes a USB port. 710 */ 711 private static final class PortInfo { 712 public static final int DISPOSITION_ADDED = 0; 713 public static final int DISPOSITION_CHANGED = 1; 714 public static final int DISPOSITION_READY = 2; 715 public static final int DISPOSITION_REMOVED = 3; 716 717 public final UsbPort mUsbPort; 718 public UsbPortStatus mUsbPortStatus; 719 public boolean mCanChangeMode; 720 public boolean mCanChangePowerRole; 721 public boolean mCanChangeDataRole; 722 public int mDisposition; // default initialized to 0 which means added 723 724 public PortInfo(String portId, int supportedModes) { 725 mUsbPort = new UsbPort(portId, supportedModes); 726 } 727 728 public boolean setStatus(int currentMode, boolean canChangeMode, 729 int currentPowerRole, boolean canChangePowerRole, 730 int currentDataRole, boolean canChangeDataRole, 731 int supportedRoleCombinations) { 732 mCanChangeMode = canChangeMode; 733 mCanChangePowerRole = canChangePowerRole; 734 mCanChangeDataRole = canChangeDataRole; 735 if (mUsbPortStatus == null 736 || mUsbPortStatus.getCurrentMode() != currentMode 737 || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole 738 || mUsbPortStatus.getCurrentDataRole() != currentDataRole 739 || mUsbPortStatus.getSupportedRoleCombinations() 740 != supportedRoleCombinations) { 741 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole, 742 supportedRoleCombinations); 743 return true; 744 } 745 return false; 746 } 747 748 @Override 749 public String toString() { 750 return "port=" + mUsbPort + ", status=" + mUsbPortStatus 751 + ", canChangeMode=" + mCanChangeMode 752 + ", canChangePowerRole=" + mCanChangePowerRole 753 + ", canChangeDataRole=" + mCanChangeDataRole; 754 } 755 } 756 757 /** 758 * Used for storing the raw data from the kernel 759 * Values of the member variables mocked directly incase of emulation. 760 */ 761 private static final class RawPortInfo implements Parcelable { 762 public final String portId; 763 public final int supportedModes; 764 public int currentMode; 765 public boolean canChangeMode; 766 public int currentPowerRole; 767 public boolean canChangePowerRole; 768 public int currentDataRole; 769 public boolean canChangeDataRole; 770 771 RawPortInfo(String portId, int supportedModes) { 772 this.portId = portId; 773 this.supportedModes = supportedModes; 774 } 775 776 RawPortInfo(String portId, int supportedModes, 777 int currentMode, boolean canChangeMode, 778 int currentPowerRole, boolean canChangePowerRole, 779 int currentDataRole, boolean canChangeDataRole) { 780 this.portId = portId; 781 this.supportedModes = supportedModes; 782 this.currentMode = currentMode; 783 this.canChangeMode = canChangeMode; 784 this.currentPowerRole = currentPowerRole; 785 this.canChangePowerRole = canChangePowerRole; 786 this.currentDataRole = currentDataRole; 787 this.canChangeDataRole = canChangeDataRole; 788 } 789 790 @Override 791 public int describeContents() { 792 return 0; 793 } 794 795 @Override 796 public void writeToParcel(Parcel dest, int flags) { 797 dest.writeString(portId); 798 dest.writeInt(supportedModes); 799 dest.writeInt(currentMode); 800 dest.writeByte((byte) (canChangeMode ? 1 : 0)); 801 dest.writeInt(currentPowerRole); 802 dest.writeByte((byte) (canChangePowerRole ? 1 : 0)); 803 dest.writeInt(currentDataRole); 804 dest.writeByte((byte) (canChangeDataRole ? 1 : 0)); 805 } 806 807 public static final Parcelable.Creator<RawPortInfo> CREATOR = 808 new Parcelable.Creator<RawPortInfo>() { 809 @Override 810 public RawPortInfo createFromParcel(Parcel in) { 811 String id = in.readString(); 812 int supportedModes = in.readInt(); 813 int currentMode = in.readInt(); 814 boolean canChangeMode = in.readByte() != 0; 815 int currentPowerRole = in.readInt(); 816 boolean canChangePowerRole = in.readByte() != 0; 817 int currentDataRole = in.readInt(); 818 boolean canChangeDataRole = in.readByte() != 0; 819 return new RawPortInfo(id, supportedModes, currentMode, canChangeMode, 820 currentPowerRole, canChangePowerRole, 821 currentDataRole, canChangeDataRole); 822 } 823 824 @Override 825 public RawPortInfo[] newArray(int size) { 826 return new RawPortInfo[size]; 827 } 828 }; 829 } 830 } 831