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