1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.usb;
18
19import com.android.internal.util.IndentingPrintWriter;
20import com.android.server.FgThread;
21
22import android.content.Context;
23import android.content.Intent;
24import android.hardware.usb.UsbManager;
25import android.hardware.usb.UsbPort;
26import android.hardware.usb.UsbPortStatus;
27import android.os.Handler;
28import android.os.Message;
29import android.os.SystemClock;
30import android.os.SystemProperties;
31import android.os.UEventObserver;
32import android.os.UserHandle;
33import android.system.ErrnoException;
34import android.system.Os;
35import android.system.OsConstants;
36import android.util.ArrayMap;
37import android.util.Log;
38import android.util.Slog;
39
40import java.io.File;
41import java.io.FileWriter;
42import java.io.IOException;
43
44import libcore.io.IoUtils;
45
46/**
47 * Allows trusted components to control the properties of physical USB ports
48 * via the "/sys/class/dual_role_usb" kernel interface.
49 * <p>
50 * Note: This interface may not be supported on all chipsets since the USB drivers
51 * must be changed to publish this information through the module.  At the moment
52 * we only need this for devices with USB Type C ports to allow the System UI to
53 * control USB charging and data direction.  On devices that do not support this
54 * interface the list of ports may incorrectly appear to be empty
55 * (but we don't care today).
56 * </p>
57 */
58public class UsbPortManager {
59    private static final String TAG = "UsbPortManager";
60
61    private static final int MSG_UPDATE_PORTS = 1;
62
63    // UEvent path to watch.
64    private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb";
65
66    // SysFS directory that contains USB ports as subdirectories.
67    private static final String SYSFS_CLASS = "/sys/class/dual_role_usb";
68
69    // SysFS file that contains a USB port's supported modes.  (read-only)
70    // Contents: "", "ufp", "dfp", or "ufp dfp".
71    private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes";
72
73    // SysFS file that contains a USB port's current mode.  (read-write if configurable)
74    // Contents: "", "ufp", or "dfp".
75    private static final String SYSFS_PORT_MODE = "mode";
76
77    // SysFS file that contains a USB port's current power role.  (read-write if configurable)
78    // Contents: "", "source", or "sink".
79    private static final String SYSFS_PORT_POWER_ROLE = "power_role";
80
81    // SysFS file that contains a USB port's current data role.  (read-write if configurable)
82    // Contents: "", "host", or "device".
83    private static final String SYSFS_PORT_DATA_ROLE = "data_role";
84
85    // Port modes: upstream facing port or downstream facing port.
86    private static final String PORT_MODE_DFP = "dfp";
87    private static final String PORT_MODE_UFP = "ufp";
88
89    // Port power roles: source or sink.
90    private static final String PORT_POWER_ROLE_SOURCE = "source";
91    private static final String PORT_POWER_ROLE_SINK = "sink";
92
93    // Port data roles: host or device.
94    private static final String PORT_DATA_ROLE_HOST = "host";
95    private static final String PORT_DATA_ROLE_DEVICE = "device";
96
97    private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec.";
98    private static final String USB_TYPEC_STATE = "sys.usb.typec.state";
99
100    // All non-trivial role combinations.
101    private static final int COMBO_SOURCE_HOST =
102            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST);
103    private static final int COMBO_SOURCE_DEVICE =
104            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE);
105    private static final int COMBO_SINK_HOST =
106            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST);
107    private static final int COMBO_SINK_DEVICE =
108            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE);
109
110    // The system context.
111    private final Context mContext;
112
113    // True if we have kernel support.
114    private final boolean mHaveKernelSupport;
115
116    // Mutex for all mutable shared state.
117    private final Object mLock = new Object();
118
119    // List of all ports, indexed by id.
120    // Ports may temporarily have different dispositions as they are added or removed
121    // but the class invariant is that this list will only contain ports with DISPOSITION_READY
122    // except while updatePortsLocked() is in progress.
123    private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>();
124
125    // List of all simulated ports, indexed by id.
126    private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts =
127            new ArrayMap<String, SimulatedPortInfo>();
128
129    public UsbPortManager(Context context) {
130        mContext = context;
131        mHaveKernelSupport = new File(SYSFS_CLASS).exists();
132    }
133
134    public void systemReady() {
135        mUEventObserver.startObserving(UEVENT_FILTER);
136        scheduleUpdatePorts();
137    }
138
139    public UsbPort[] getPorts() {
140        synchronized (mLock) {
141            final int count = mPorts.size();
142            final UsbPort[] result = new UsbPort[count];
143            for (int i = 0; i < count; i++) {
144                result[i] = mPorts.valueAt(i).mUsbPort;
145            }
146            return result;
147        }
148    }
149
150    public UsbPortStatus getPortStatus(String portId) {
151        synchronized (mLock) {
152            final PortInfo portInfo = mPorts.get(portId);
153            return portInfo != null ? portInfo.mUsbPortStatus : null;
154        }
155    }
156
157    public void setPortRoles(String portId, int newPowerRole, int newDataRole,
158            IndentingPrintWriter pw) {
159        synchronized (mLock) {
160            final PortInfo portInfo = mPorts.get(portId);
161            if (portInfo == null) {
162                if (pw != null) {
163                    pw.println("No such USB port: " + portId);
164                }
165                return;
166            }
167
168            // Check whether the new role is actually supported.
169            if (!portInfo.mUsbPortStatus.isRoleCombinationSupported(newPowerRole, newDataRole)) {
170                logAndPrint(Log.ERROR, pw, "Attempted to set USB port into unsupported "
171                        + "role combination: portId=" + portId
172                        + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
173                        + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
174                return;
175            }
176
177            // Check whether anything actually changed.
178            final int currentDataRole = portInfo.mUsbPortStatus.getCurrentDataRole();
179            final int currentPowerRole = portInfo.mUsbPortStatus.getCurrentPowerRole();
180            if (currentDataRole == newDataRole && currentPowerRole == newPowerRole) {
181                if (pw != null) {
182                    pw.println("No change.");
183                }
184                return;
185            }
186
187            // Determine whether we need to change the mode in order to accomplish this goal.
188            // We prefer not to do this since it's more likely to fail.
189            //
190            // Note: Arguably it might be worth allowing the client to influence this policy
191            // decision so that we could show more powerful developer facing UI but let's
192            // see how far we can get without having to do that.
193            final boolean canChangeMode = portInfo.mCanChangeMode;
194            final boolean canChangePowerRole = portInfo.mCanChangePowerRole;
195            final boolean canChangeDataRole = portInfo.mCanChangeDataRole;
196            final int currentMode = portInfo.mUsbPortStatus.getCurrentMode();
197            final int newMode;
198            if ((!canChangePowerRole && currentPowerRole != newPowerRole)
199                    || (!canChangeDataRole && currentDataRole != newDataRole)) {
200                if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE
201                        && newDataRole == UsbPort.DATA_ROLE_HOST) {
202                    newMode = UsbPort.MODE_DFP;
203                } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK
204                        && newDataRole == UsbPort.DATA_ROLE_DEVICE) {
205                    newMode = UsbPort.MODE_UFP;
206                } else {
207                    logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations "
208                            + "while attempting to change role: " + portInfo
209                            + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
210                            + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
211                    return;
212                }
213            } else {
214                newMode = currentMode;
215            }
216
217            // Make it happen.
218            logAndPrint(Log.INFO, pw, "Setting USB port mode and role: portId=" + portId
219                    + ", currentMode=" + UsbPort.modeToString(currentMode)
220                    + ", currentPowerRole=" + UsbPort.powerRoleToString(currentPowerRole)
221                    + ", currentDataRole=" + UsbPort.dataRoleToString(currentDataRole)
222                    + ", newMode=" + UsbPort.modeToString(newMode)
223                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
224                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
225
226            SimulatedPortInfo sim = mSimulatedPorts.get(portId);
227            if (sim != null) {
228                // Change simulated state.
229                sim.mCurrentMode = newMode;
230                sim.mCurrentPowerRole = newPowerRole;
231                sim.mCurrentDataRole = newDataRole;
232            } else if (mHaveKernelSupport) {
233                // Change actual state.
234                final File portDir = new File(SYSFS_CLASS, portId);
235                if (!portDir.exists()) {
236                    logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId);
237                    return;
238                }
239
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                    if (!writeFile(portDir, SYSFS_PORT_MODE,
248                            newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) {
249                        logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
250                                + "portId=" + portId
251                                + ", newMode=" + UsbPort.modeToString(newMode));
252                        return;
253                    }
254                } else {
255                    // Change power and data role independently as needed.
256                    if (currentPowerRole != newPowerRole) {
257                        if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE,
258                                newPowerRole == UsbPort.POWER_ROLE_SOURCE
259                                ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) {
260                            logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: "
261                                    + "portId=" + portId
262                                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole));
263                            return;
264                        }
265                    }
266                    if (currentDataRole != newDataRole) {
267                        if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE,
268                                newDataRole == UsbPort.DATA_ROLE_HOST
269                                ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) {
270                            logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: "
271                                    + "portId=" + portId
272                                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
273                            return;
274                        }
275                    }
276                }
277            }
278            updatePortsLocked(pw);
279        }
280    }
281
282    public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
283        synchronized (mLock) {
284            if (mSimulatedPorts.containsKey(portId)) {
285                pw.println("Port with same name already exists.  Please remove it first.");
286                return;
287            }
288
289            pw.println("Adding simulated port: portId=" + portId
290                    + ", supportedModes=" + UsbPort.modeToString(supportedModes));
291            mSimulatedPorts.put(portId,
292                    new SimulatedPortInfo(portId, supportedModes));
293            updatePortsLocked(pw);
294        }
295    }
296
297    public void connectSimulatedPort(String portId, int mode, boolean canChangeMode,
298            int powerRole, boolean canChangePowerRole,
299            int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) {
300        synchronized (mLock) {
301            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
302            if (portInfo == null) {
303                pw.println("Cannot connect simulated port which does not exist.");
304                return;
305            }
306
307            if (mode == 0 || powerRole == 0 || dataRole == 0) {
308                pw.println("Cannot connect simulated port in null mode, "
309                        + "power role, or data role.");
310                return;
311            }
312
313            if ((portInfo.mSupportedModes & mode) == 0) {
314                pw.println("Simulated port does not support mode: " + UsbPort.modeToString(mode));
315                return;
316            }
317
318            pw.println("Connecting simulated port: portId=" + portId
319                    + ", mode=" + UsbPort.modeToString(mode)
320                    + ", canChangeMode=" + canChangeMode
321                    + ", powerRole=" + UsbPort.powerRoleToString(powerRole)
322                    + ", canChangePowerRole=" + canChangePowerRole
323                    + ", dataRole=" + UsbPort.dataRoleToString(dataRole)
324                    + ", canChangeDataRole=" + canChangeDataRole);
325            portInfo.mCurrentMode = mode;
326            portInfo.mCanChangeMode = canChangeMode;
327            portInfo.mCurrentPowerRole = powerRole;
328            portInfo.mCanChangePowerRole = canChangePowerRole;
329            portInfo.mCurrentDataRole = dataRole;
330            portInfo.mCanChangeDataRole = canChangeDataRole;
331            updatePortsLocked(pw);
332        }
333    }
334
335    public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
336        synchronized (mLock) {
337            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
338            if (portInfo == null) {
339                pw.println("Cannot disconnect simulated port which does not exist.");
340                return;
341            }
342
343            pw.println("Disconnecting simulated port: portId=" + portId);
344            portInfo.mCurrentMode = 0;
345            portInfo.mCanChangeMode = false;
346            portInfo.mCurrentPowerRole = 0;
347            portInfo.mCanChangePowerRole = false;
348            portInfo.mCurrentDataRole = 0;
349            portInfo.mCanChangeDataRole = false;
350            updatePortsLocked(pw);
351        }
352    }
353
354    public void removeSimulatedPort(String portId, IndentingPrintWriter pw) {
355        synchronized (mLock) {
356            final int index = mSimulatedPorts.indexOfKey(portId);
357            if (index < 0) {
358                pw.println("Cannot remove simulated port which does not exist.");
359                return;
360            }
361
362            pw.println("Disconnecting simulated port: portId=" + portId);
363            mSimulatedPorts.removeAt(index);
364            updatePortsLocked(pw);
365        }
366    }
367
368    public void resetSimulation(IndentingPrintWriter pw) {
369        synchronized (mLock) {
370            pw.println("Removing all simulated ports and ending simulation.");
371            if (!mSimulatedPorts.isEmpty()) {
372                mSimulatedPorts.clear();
373                updatePortsLocked(pw);
374            }
375        }
376    }
377
378    public void dump(IndentingPrintWriter pw) {
379        synchronized (mLock) {
380            pw.print("USB Port State:");
381            if (!mSimulatedPorts.isEmpty()) {
382                pw.print(" (simulation active; end with 'dumpsys usb reset')");
383            }
384            pw.println();
385
386            if (mPorts.isEmpty()) {
387                pw.println("  <no ports>");
388            } else {
389                for (PortInfo portInfo : mPorts.values()) {
390                    pw.println("  " + portInfo.mUsbPort.getId() + ": " + portInfo);
391                }
392            }
393        }
394    }
395
396    private void updatePortsLocked(IndentingPrintWriter pw) {
397        // Assume all ports are gone unless informed otherwise.
398        // Kind of pessimistic but simple.
399        for (int i = mPorts.size(); i-- > 0; ) {
400            mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED;
401        }
402
403        // Enumerate all extant ports.
404        if (!mSimulatedPorts.isEmpty()) {
405            final int count = mSimulatedPorts.size();
406            for (int i = 0; i < count; i++) {
407                final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i);
408                addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes,
409                        portInfo.mCurrentMode, portInfo.mCanChangeMode,
410                        portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole,
411                        portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw);
412            }
413        } else if (mHaveKernelSupport) {
414            final File[] portDirs = new File(SYSFS_CLASS).listFiles();
415            if (portDirs != null) {
416                for (File portDir : portDirs) {
417                    if (!portDir.isDirectory()) {
418                        continue;
419                    }
420
421                    // Parse the sysfs file contents.
422                    final String portId = portDir.getName();
423                    final int supportedModes = readSupportedModes(portDir);
424                    final int currentMode = readCurrentMode(portDir);
425                    final boolean canChangeMode = canChangeMode(portDir);
426                    final int currentPowerRole = readCurrentPowerRole(portDir);
427                    final boolean canChangePowerRole = canChangePowerRole(portDir);
428                    final int currentDataRole = readCurrentDataRole(portDir);
429                    final boolean canChangeDataRole = canChangeDataRole(portDir);
430                    addOrUpdatePortLocked(portId, supportedModes,
431                            currentMode, canChangeMode,
432                            currentPowerRole, canChangePowerRole,
433                            currentDataRole, canChangeDataRole, pw);
434                 }
435            }
436        }
437
438        // Process the updates.
439        // Once finished, the list of ports will only contain ports in DISPOSITION_READY.
440        for (int i = mPorts.size(); i-- > 0; ) {
441            final PortInfo portInfo = mPorts.valueAt(i);
442            switch (portInfo.mDisposition) {
443                case PortInfo.DISPOSITION_ADDED:
444                    handlePortAddedLocked(portInfo, pw);
445                    portInfo.mDisposition = PortInfo.DISPOSITION_READY;
446                    break;
447                case PortInfo.DISPOSITION_CHANGED:
448                    handlePortChangedLocked(portInfo, pw);
449                    portInfo.mDisposition = PortInfo.DISPOSITION_READY;
450                    break;
451                case PortInfo.DISPOSITION_REMOVED:
452                    mPorts.removeAt(i);
453                    portInfo.mUsbPortStatus = null; // must do this early
454                    handlePortRemovedLocked(portInfo, pw);
455                    break;
456            }
457        }
458    }
459
460    // Must only be called by updatePortsLocked.
461    private void addOrUpdatePortLocked(String portId, int supportedModes,
462            int currentMode, boolean canChangeMode,
463            int currentPowerRole, boolean canChangePowerRole,
464            int currentDataRole, boolean canChangeDataRole,
465            IndentingPrintWriter pw) {
466        // Only allow mode switch capability for dual role ports.
467        // Validate that the current mode matches the supported modes we expect.
468        if (supportedModes != UsbPort.MODE_DUAL) {
469            canChangeMode = false;
470            if (currentMode != 0 && currentMode != supportedModes) {
471                logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
472                        + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
473                        + ", currentMode=" + UsbPort.modeToString(currentMode));
474                currentMode = 0;
475            }
476        }
477
478        // Determine the supported role combinations.
479        // Note that the policy is designed to prefer setting the power and data
480        // role independently rather than changing the mode.
481        int supportedRoleCombinations = UsbPort.combineRolesAsBit(
482                currentPowerRole, currentDataRole);
483        if (currentMode != 0 && currentPowerRole != 0 && currentDataRole != 0) {
484            if (canChangePowerRole && canChangeDataRole) {
485                // Can change both power and data role independently.
486                // Assume all combinations are possible.
487                supportedRoleCombinations |=
488                        COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
489                                | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
490            } else if (canChangePowerRole) {
491                // Can only change power role.
492                // Assume data role must remain at its current value.
493                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
494                        UsbPort.POWER_ROLE_SOURCE, currentDataRole);
495                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
496                        UsbPort.POWER_ROLE_SINK, currentDataRole);
497            } else if (canChangeDataRole) {
498                // Can only change data role.
499                // Assume power role must remain at its current value.
500                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
501                        currentPowerRole, UsbPort.DATA_ROLE_HOST);
502                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
503                        currentPowerRole, UsbPort.DATA_ROLE_DEVICE);
504            } else if (canChangeMode) {
505                // Can only change the mode.
506                // Assume both standard UFP and DFP configurations will become available
507                // when this happens.
508                supportedRoleCombinations |= COMBO_SOURCE_HOST | COMBO_SINK_DEVICE;
509            }
510        }
511
512        // Update the port data structures.
513        PortInfo portInfo = mPorts.get(portId);
514        if (portInfo == null) {
515            portInfo = new PortInfo(portId, supportedModes);
516            portInfo.setStatus(currentMode, canChangeMode,
517                    currentPowerRole, canChangePowerRole,
518                    currentDataRole, canChangeDataRole,
519                    supportedRoleCombinations);
520            mPorts.put(portId, portInfo);
521        } else {
522            // Sanity check that ports aren't changing definition out from under us.
523            if (supportedModes != portInfo.mUsbPort.getSupportedModes()) {
524                logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from "
525                        + "USB port driver (should be immutable): "
526                        + "previous=" + UsbPort.modeToString(
527                                portInfo.mUsbPort.getSupportedModes())
528                        + ", current=" + UsbPort.modeToString(supportedModes));
529            }
530
531            if (portInfo.setStatus(currentMode, canChangeMode,
532                    currentPowerRole, canChangePowerRole,
533                    currentDataRole, canChangeDataRole,
534                    supportedRoleCombinations)) {
535                portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
536            } else {
537                portInfo.mDisposition = PortInfo.DISPOSITION_READY;
538            }
539        }
540    }
541
542    private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
543        logAndPrint(Log.INFO, pw, "USB port added: " + portInfo);
544        sendPortChangedBroadcastLocked(portInfo);
545    }
546
547    private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
548        logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
549        sendPortChangedBroadcastLocked(portInfo);
550    }
551
552    private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
553        logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
554        sendPortChangedBroadcastLocked(portInfo);
555    }
556
557    private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
558        final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);
559        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
560        intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort);
561        intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
562
563        // Guard against possible reentrance by posting the broadcast from the handler
564        // instead of from within the critical section.
565        mHandler.post(new Runnable() {
566            @Override
567            public void run() {
568                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
569            }
570        });
571    }
572
573    private void scheduleUpdatePorts() {
574        if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) {
575            mHandler.sendEmptyMessage(MSG_UPDATE_PORTS);
576        }
577    }
578
579    private static int readSupportedModes(File portDir) {
580        int modes = 0;
581        final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES);
582        if (contents != null) {
583            if (contents.contains(PORT_MODE_DFP)) {
584                modes |= UsbPort.MODE_DFP;
585            }
586            if (contents.contains(PORT_MODE_UFP)) {
587                modes |= UsbPort.MODE_UFP;
588            }
589        }
590        return modes;
591    }
592
593    private static int readCurrentMode(File portDir) {
594        final String contents = readFile(portDir, SYSFS_PORT_MODE);
595        if (contents != null) {
596            if (contents.equals(PORT_MODE_DFP)) {
597                return UsbPort.MODE_DFP;
598            }
599            if (contents.equals(PORT_MODE_UFP)) {
600                return UsbPort.MODE_UFP;
601            }
602        }
603        return 0;
604    }
605
606    private static int readCurrentPowerRole(File portDir) {
607        final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE);
608        if (contents != null) {
609            if (contents.equals(PORT_POWER_ROLE_SOURCE)) {
610                return UsbPort.POWER_ROLE_SOURCE;
611            }
612            if (contents.equals(PORT_POWER_ROLE_SINK)) {
613                return UsbPort.POWER_ROLE_SINK;
614            }
615        }
616        return 0;
617    }
618
619    private static int readCurrentDataRole(File portDir) {
620        final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE);
621        if (contents != null) {
622            if (contents.equals(PORT_DATA_ROLE_HOST)) {
623                return UsbPort.DATA_ROLE_HOST;
624            }
625            if (contents.equals(PORT_DATA_ROLE_DEVICE)) {
626                return UsbPort.DATA_ROLE_DEVICE;
627            }
628        }
629        return 0;
630    }
631
632    private static boolean fileIsRootWritable(String path) {
633        try {
634            // If the file is user writable, then it is root writable.
635            return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0;
636        } catch (ErrnoException e) {
637            return false;
638        }
639    }
640
641    private static boolean canChangeMode(File portDir) {
642        return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath());
643    }
644
645    private static boolean canChangePowerRole(File portDir) {
646        return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath());
647    }
648
649    private static boolean canChangeDataRole(File portDir) {
650        return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath());
651    }
652
653    private static String readFile(File dir, String filename) {
654        final File file = new File(dir, filename);
655        try {
656            return IoUtils.readFileAsString(file.getAbsolutePath()).trim();
657        } catch (IOException ex) {
658            return null;
659        }
660    }
661
662    private static boolean waitForState(String property, String state) {
663        // wait for the transition to complete.
664        // give up after 5 seconds.
665        // 5 seconds is probably too long, but we have seen hardware that takes
666        // over 3 seconds to change states.
667        String value = null;
668        for (int i = 0; i < 100; i++) {
669            // State transition is done when property is set to the new configuration
670            value = SystemProperties.get(property);
671            if (state.equals(value)) return true;
672            SystemClock.sleep(50);
673        }
674        Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value);
675        return false;
676    }
677
678    private static String propertyFromFilename(String filename) {
679        return USB_TYPEC_PROP_PREFIX + filename;
680    }
681
682    private static boolean writeFile(File dir, String filename, String contents) {
683        SystemProperties.set(propertyFromFilename(filename), contents);
684        return waitForState(USB_TYPEC_STATE, contents);
685    }
686
687    private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
688        Slog.println(priority, TAG, msg);
689        if (pw != null) {
690            pw.println(msg);
691        }
692    }
693
694    private final Handler mHandler = new Handler(FgThread.get().getLooper()) {
695        @Override
696        public void handleMessage(Message msg) {
697            switch (msg.what) {
698                case MSG_UPDATE_PORTS: {
699                    synchronized (mLock) {
700                        updatePortsLocked(null);
701                    }
702                    break;
703                }
704            }
705        }
706    };
707
708    private final UEventObserver mUEventObserver = new UEventObserver() {
709        @Override
710        public void onUEvent(UEvent event) {
711            scheduleUpdatePorts();
712        }
713    };
714
715    /**
716     * Describes a USB port.
717     */
718    private static final class PortInfo {
719        public static final int DISPOSITION_ADDED = 0;
720        public static final int DISPOSITION_CHANGED = 1;
721        public static final int DISPOSITION_READY = 2;
722        public static final int DISPOSITION_REMOVED = 3;
723
724        public final UsbPort mUsbPort;
725        public UsbPortStatus mUsbPortStatus;
726        public boolean mCanChangeMode;
727        public boolean mCanChangePowerRole;
728        public boolean mCanChangeDataRole;
729        public int mDisposition; // default initialized to 0 which means added
730
731        public PortInfo(String portId, int supportedModes) {
732            mUsbPort = new UsbPort(portId, supportedModes);
733        }
734
735        public boolean setStatus(int currentMode, boolean canChangeMode,
736                int currentPowerRole, boolean canChangePowerRole,
737                int currentDataRole, boolean canChangeDataRole,
738                int supportedRoleCombinations) {
739            mCanChangeMode = canChangeMode;
740            mCanChangePowerRole = canChangePowerRole;
741            mCanChangeDataRole = canChangeDataRole;
742            if (mUsbPortStatus == null
743                    || mUsbPortStatus.getCurrentMode() != currentMode
744                    || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
745                    || mUsbPortStatus.getCurrentDataRole() != currentDataRole
746                    || mUsbPortStatus.getSupportedRoleCombinations()
747                            != supportedRoleCombinations) {
748                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
749                        supportedRoleCombinations);
750                return true;
751            }
752            return false;
753        }
754
755        @Override
756        public String toString() {
757            return "port=" + mUsbPort + ", status=" + mUsbPortStatus
758                    + ", canChangeMode=" + mCanChangeMode
759                    + ", canChangePowerRole=" + mCanChangePowerRole
760                    + ", canChangeDataRole=" + mCanChangeDataRole;
761        }
762    }
763
764    /**
765     * Describes a simulated USB port.
766     * Roughly mirrors the information we would ordinarily get from the kernel.
767     */
768    private static final class SimulatedPortInfo {
769        public final String mPortId;
770        public final int mSupportedModes;
771        public int mCurrentMode;
772        public boolean mCanChangeMode;
773        public int mCurrentPowerRole;
774        public boolean mCanChangePowerRole;
775        public int mCurrentDataRole;
776        public boolean mCanChangeDataRole;
777
778        public SimulatedPortInfo(String portId, int supportedModes) {
779            mPortId = portId;
780            mSupportedModes = supportedModes;
781        }
782    }
783}
784