Home | History | Annotate | Download | only in usb
      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