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 static com.android.internal.usb.DumpUtils.writePort;
     20 import static com.android.internal.usb.DumpUtils.writePortStatus;
     21 
     22 import android.annotation.NonNull;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.hardware.usb.UsbManager;
     26 import android.hardware.usb.UsbPort;
     27 import android.hardware.usb.UsbPortStatus;
     28 import android.hardware.usb.V1_0.IUsb;
     29 import android.hardware.usb.V1_0.PortRole;
     30 import android.hardware.usb.V1_0.PortRoleType;
     31 import android.hardware.usb.V1_0.PortStatus;
     32 import android.hardware.usb.V1_0.Status;
     33 import android.hardware.usb.V1_1.IUsbCallback;
     34 import android.hardware.usb.V1_1.PortStatus_1_1;
     35 import android.hidl.manager.V1_0.IServiceManager;
     36 import android.hidl.manager.V1_0.IServiceNotification;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.HwBinder;
     40 import android.os.Message;
     41 import android.os.Parcel;
     42 import android.os.Parcelable;
     43 import android.os.RemoteException;
     44 import android.os.UserHandle;
     45 import android.service.usb.UsbPortInfoProto;
     46 import android.service.usb.UsbPortManagerProto;
     47 import android.util.ArrayMap;
     48 import android.util.Log;
     49 import android.util.Slog;
     50 
     51 import com.android.internal.annotations.GuardedBy;
     52 import com.android.internal.util.IndentingPrintWriter;
     53 import com.android.internal.util.dump.DualDumpOutputStream;
     54 import com.android.server.FgThread;
     55 
     56 import java.util.ArrayList;
     57 import 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  */
     71 public 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 (at) 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