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