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