176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown/*
276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * Copyright (C) 2015 The Android Open Source Project
376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown *
476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * Licensed under the Apache License, Version 2.0 (the "License");
576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * you may not use this file except in compliance with the License.
676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * You may obtain a copy of the License at
776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown *
876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown *      http://www.apache.org/licenses/LICENSE-2.0
976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown *
1076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * Unless required by applicable law or agreed to in writing, software
1176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * distributed under the License is distributed on an "AS IS" BASIS,
1276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * See the License for the specific language governing permissions and
1476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * limitations under the License.
1576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown */
1676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
1776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownpackage com.android.server.usb;
1876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
1976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport com.android.internal.util.IndentingPrintWriter;
2076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport com.android.server.FgThread;
2176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
2276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.content.Context;
2376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.content.Intent;
2476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.hardware.usb.UsbManager;
2576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.hardware.usb.UsbPort;
2676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.hardware.usb.UsbPortStatus;
2776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.os.Handler;
2876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.os.Message;
296ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbournimport android.os.SystemClock;
306ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbournimport android.os.SystemProperties;
3176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.os.UEventObserver;
3276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.os.UserHandle;
336ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbournimport android.system.ErrnoException;
346ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbournimport android.system.Os;
356ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbournimport android.system.OsConstants;
3676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.util.ArrayMap;
3776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.util.Log;
3876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport android.util.Slog;
3976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
4076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport java.io.File;
4176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport java.io.FileWriter;
4276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport java.io.IOException;
4376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
4476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownimport libcore.io.IoUtils;
4576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
4676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown/**
4776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * Allows trusted components to control the properties of physical USB ports
4876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * via the "/sys/class/dual_role_usb" kernel interface.
4976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * <p>
5076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * Note: This interface may not be supported on all chipsets since the USB drivers
5176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * must be changed to publish this information through the module.  At the moment
5276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * we only need this for devices with USB Type C ports to allow the System UI to
5376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * control USB charging and data direction.  On devices that do not support this
5476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * interface the list of ports may incorrectly appear to be empty
5576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * (but we don't care today).
5676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown * </p>
5776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown */
5876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brownpublic class UsbPortManager {
5976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String TAG = "UsbPortManager";
6076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
6176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final int MSG_UPDATE_PORTS = 1;
6276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
6376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // UEvent path to watch.
6476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String UEVENT_FILTER = "SUBSYSTEM=dual_role_usb";
6576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
6676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // SysFS directory that contains USB ports as subdirectories.
6776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String SYSFS_CLASS = "/sys/class/dual_role_usb";
6876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
6976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // SysFS file that contains a USB port's supported modes.  (read-only)
7076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Contents: "", "ufp", "dfp", or "ufp dfp".
7176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String SYSFS_PORT_SUPPORTED_MODES = "supported_modes";
7276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
7376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // SysFS file that contains a USB port's current mode.  (read-write if configurable)
7476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Contents: "", "ufp", or "dfp".
7576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String SYSFS_PORT_MODE = "mode";
7676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
7776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // SysFS file that contains a USB port's current power role.  (read-write if configurable)
7876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Contents: "", "source", or "sink".
7976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String SYSFS_PORT_POWER_ROLE = "power_role";
8076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
8176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // SysFS file that contains a USB port's current data role.  (read-write if configurable)
8276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Contents: "", "host", or "device".
8376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String SYSFS_PORT_DATA_ROLE = "data_role";
8476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
8576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Port modes: upstream facing port or downstream facing port.
8676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String PORT_MODE_DFP = "dfp";
8776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String PORT_MODE_UFP = "ufp";
8876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
8976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Port power roles: source or sink.
9076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String PORT_POWER_ROLE_SOURCE = "source";
9176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String PORT_POWER_ROLE_SINK = "sink";
9276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
9376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Port data roles: host or device.
9476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String PORT_DATA_ROLE_HOST = "host";
9576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final String PORT_DATA_ROLE_DEVICE = "device";
9676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
976ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    private static final String USB_TYPEC_PROP_PREFIX = "sys.usb.typec.";
986ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    private static final String USB_TYPEC_STATE = "sys.usb.typec.state";
996ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn
10076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // All non-trivial role combinations.
10176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final int COMBO_SOURCE_HOST =
10276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_HOST);
10376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final int COMBO_SOURCE_DEVICE =
10476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SOURCE, UsbPort.DATA_ROLE_DEVICE);
10576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final int COMBO_SINK_HOST =
10676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_HOST);
10776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final int COMBO_SINK_DEVICE =
10876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            UsbPort.combineRolesAsBit(UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE);
10976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
11076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // The system context.
11176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final Context mContext;
11276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
11376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // True if we have kernel support.
11476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final boolean mHaveKernelSupport;
11576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
11676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Mutex for all mutable shared state.
11776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final Object mLock = new Object();
11876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
11976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // List of all ports, indexed by id.
12076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Ports may temporarily have different dispositions as they are added or removed
12176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // but the class invariant is that this list will only contain ports with DISPOSITION_READY
12276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // except while updatePortsLocked() is in progress.
12376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<String, PortInfo>();
12476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
12576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // List of all simulated ports, indexed by id.
12676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final ArrayMap<String, SimulatedPortInfo> mSimulatedPorts =
12776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            new ArrayMap<String, SimulatedPortInfo>();
12876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
12976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public UsbPortManager(Context context) {
13076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        mContext = context;
13176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        mHaveKernelSupport = new File(SYSFS_CLASS).exists();
13276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
13376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
13476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void systemReady() {
13576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        mUEventObserver.startObserving(UEVENT_FILTER);
13676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        scheduleUpdatePorts();
13776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
13876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
13976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public UsbPort[] getPorts() {
14076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
14176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int count = mPorts.size();
14276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final UsbPort[] result = new UsbPort[count];
14376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            for (int i = 0; i < count; i++) {
14476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                result[i] = mPorts.valueAt(i).mUsbPort;
14576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
14676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            return result;
14776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
14876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
14976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
15076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public UsbPortStatus getPortStatus(String portId) {
15176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
15276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final PortInfo portInfo = mPorts.get(portId);
15376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            return portInfo != null ? portInfo.mUsbPortStatus : null;
15476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
15576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
15676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
15776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void setPortRoles(String portId, int newPowerRole, int newDataRole,
15876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            IndentingPrintWriter pw) {
15976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
16076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final PortInfo portInfo = mPorts.get(portId);
16176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (portInfo == null) {
16276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                if (pw != null) {
16376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    pw.println("No such USB port: " + portId);
16476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
16576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
16676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
16776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
16876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // Check whether the new role is actually supported.
16976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (!portInfo.mUsbPortStatus.isRoleCombinationSupported(newPowerRole, newDataRole)) {
17076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                logAndPrint(Log.ERROR, pw, "Attempted to set USB port into unsupported "
17176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + "role combination: portId=" + portId
17276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
17376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
17476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
17576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
17676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
17776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // Check whether anything actually changed.
17876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int currentDataRole = portInfo.mUsbPortStatus.getCurrentDataRole();
17976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int currentPowerRole = portInfo.mUsbPortStatus.getCurrentPowerRole();
18076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (currentDataRole == newDataRole && currentPowerRole == newPowerRole) {
18176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                if (pw != null) {
18276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    pw.println("No change.");
18376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
18476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
18576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
18676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
18776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // Determine whether we need to change the mode in order to accomplish this goal.
18876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // We prefer not to do this since it's more likely to fail.
18976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            //
19076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // Note: Arguably it might be worth allowing the client to influence this policy
19176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // decision so that we could show more powerful developer facing UI but let's
19276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // see how far we can get without having to do that.
19376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final boolean canChangeMode = portInfo.mCanChangeMode;
19476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final boolean canChangePowerRole = portInfo.mCanChangePowerRole;
19576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final boolean canChangeDataRole = portInfo.mCanChangeDataRole;
19676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int currentMode = portInfo.mUsbPortStatus.getCurrentMode();
19776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int newMode;
19876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if ((!canChangePowerRole && currentPowerRole != newPowerRole)
19976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    || (!canChangeDataRole && currentDataRole != newDataRole)) {
20076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SOURCE
20176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        && newDataRole == UsbPort.DATA_ROLE_HOST) {
20276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    newMode = UsbPort.MODE_DFP;
20376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                } else if (canChangeMode && newPowerRole == UsbPort.POWER_ROLE_SINK
20476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        && newDataRole == UsbPort.DATA_ROLE_DEVICE) {
20576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    newMode = UsbPort.MODE_UFP;
20676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                } else {
20776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations "
20876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            + "while attempting to change role: " + portInfo
20976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
21076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
21176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    return;
21276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
21376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else {
21476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                newMode = currentMode;
21576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
21676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
21776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // Make it happen.
21876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            logAndPrint(Log.INFO, pw, "Setting USB port mode and role: portId=" + portId
21976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", currentMode=" + UsbPort.modeToString(currentMode)
22076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", currentPowerRole=" + UsbPort.powerRoleToString(currentPowerRole)
22176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", currentDataRole=" + UsbPort.dataRoleToString(currentDataRole)
22276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", newMode=" + UsbPort.modeToString(newMode)
22376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
22476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
22576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
22676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            SimulatedPortInfo sim = mSimulatedPorts.get(portId);
22776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (sim != null) {
22876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Change simulated state.
22976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                sim.mCurrentMode = newMode;
23076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                sim.mCurrentPowerRole = newPowerRole;
23176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                sim.mCurrentDataRole = newDataRole;
23276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else if (mHaveKernelSupport) {
23376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Change actual state.
23476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                final File portDir = new File(SYSFS_CLASS, portId);
23576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                if (!portDir.exists()) {
23676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    logAndPrint(Log.ERROR, pw, "USB port not found: portId=" + portId);
23776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    return;
23876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
23976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
24076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                if (currentMode != newMode) {
24176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // Changing the mode will have the side-effect of also changing
24276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // the power and data roles but it might take some time to apply
24376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // and the renegotiation might fail.  Due to limitations of the USB
24476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // hardware, we have no way of knowing whether it will work apriori
24576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // which is why we would prefer to set the power and data roles
24676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // directly instead.
24776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    if (!writeFile(portDir, SYSFS_PORT_MODE,
24876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            newMode == UsbPort.MODE_DFP ? PORT_MODE_DFP : PORT_MODE_UFP)) {
24976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        logAndPrint(Log.ERROR, pw, "Failed to set the USB port mode: "
25076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                + "portId=" + portId
25176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                + ", newMode=" + UsbPort.modeToString(newMode));
25276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        return;
25376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    }
25476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                } else {
25576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // Change power and data role independently as needed.
25676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    if (currentPowerRole != newPowerRole) {
25776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        if (!writeFile(portDir, SYSFS_PORT_POWER_ROLE,
25876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                newPowerRole == UsbPort.POWER_ROLE_SOURCE
25976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                ? PORT_POWER_ROLE_SOURCE : PORT_POWER_ROLE_SINK)) {
26076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            logAndPrint(Log.ERROR, pw, "Failed to set the USB port power role: "
26176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                    + "portId=" + portId
26276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                    + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole));
26376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            return;
26476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        }
26576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    }
26676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    if (currentDataRole != newDataRole) {
26776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        if (!writeFile(portDir, SYSFS_PORT_DATA_ROLE,
26876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                newDataRole == UsbPort.DATA_ROLE_HOST
26976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                ? PORT_DATA_ROLE_HOST : PORT_DATA_ROLE_DEVICE)) {
27076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            logAndPrint(Log.ERROR, pw, "Failed to set the USB port data role: "
27176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                    + "portId=" + portId
27276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                    + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
27376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            return;
27476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        }
27576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    }
27676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
27776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
27876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            updatePortsLocked(pw);
27976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
28076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
28176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
28276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
28376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
28476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (mSimulatedPorts.containsKey(portId)) {
28576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("Port with same name already exists.  Please remove it first.");
28676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
28776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
28876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
28976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println("Adding simulated port: portId=" + portId
29076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", supportedModes=" + UsbPort.modeToString(supportedModes));
29176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mSimulatedPorts.put(portId,
29276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    new SimulatedPortInfo(portId, supportedModes));
29376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            updatePortsLocked(pw);
29476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
29576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
29676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
29776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void connectSimulatedPort(String portId, int mode, boolean canChangeMode,
29876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            int powerRole, boolean canChangePowerRole,
29976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) {
30076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
30176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
30276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (portInfo == null) {
30376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("Cannot connect simulated port which does not exist.");
30476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
30576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
30676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
30776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (mode == 0 || powerRole == 0 || dataRole == 0) {
30876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("Cannot connect simulated port in null mode, "
30976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + "power role, or data role.");
31076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
31176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
31276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
31376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if ((portInfo.mSupportedModes & mode) == 0) {
31476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("Simulated port does not support mode: " + UsbPort.modeToString(mode));
31576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
31676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
31776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
31876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println("Connecting simulated port: portId=" + portId
31976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", mode=" + UsbPort.modeToString(mode)
32076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", canChangeMode=" + canChangeMode
32176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", powerRole=" + UsbPort.powerRoleToString(powerRole)
32276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", canChangePowerRole=" + canChangePowerRole
32376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", dataRole=" + UsbPort.dataRoleToString(dataRole)
32476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", canChangeDataRole=" + canChangeDataRole);
32576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCurrentMode = mode;
32676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCanChangeMode = canChangeMode;
32776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCurrentPowerRole = powerRole;
32876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCanChangePowerRole = canChangePowerRole;
32976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCurrentDataRole = dataRole;
33076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCanChangeDataRole = canChangeDataRole;
33176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            updatePortsLocked(pw);
33276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
33376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
33476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
33576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
33676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
33776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final SimulatedPortInfo portInfo = mSimulatedPorts.get(portId);
33876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (portInfo == null) {
33976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("Cannot disconnect simulated port which does not exist.");
34076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
34176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
34276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
34376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println("Disconnecting simulated port: portId=" + portId);
34476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCurrentMode = 0;
34576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCanChangeMode = false;
34676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCurrentPowerRole = 0;
34776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCanChangePowerRole = false;
34876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCurrentDataRole = 0;
34976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.mCanChangeDataRole = false;
35076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            updatePortsLocked(pw);
35176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
35276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
35376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
35476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void removeSimulatedPort(String portId, IndentingPrintWriter pw) {
35576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
35676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int index = mSimulatedPorts.indexOfKey(portId);
35776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (index < 0) {
35876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("Cannot remove simulated port which does not exist.");
35976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return;
36076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
36176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
36276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println("Disconnecting simulated port: portId=" + portId);
36376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mSimulatedPorts.removeAt(index);
36476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            updatePortsLocked(pw);
36576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
36676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
36776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
36876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void resetSimulation(IndentingPrintWriter pw) {
36976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
37076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println("Removing all simulated ports and ending simulation.");
37176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (!mSimulatedPorts.isEmpty()) {
37276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                mSimulatedPorts.clear();
37376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                updatePortsLocked(pw);
37476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
37576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
37676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
37776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
37876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    public void dump(IndentingPrintWriter pw) {
37976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        synchronized (mLock) {
38076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.print("USB Port State:");
38176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (!mSimulatedPorts.isEmpty()) {
38276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.print(" (simulation active; end with 'dumpsys usb reset')");
38376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
38476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println();
38576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
38676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (mPorts.isEmpty()) {
38776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                pw.println("  <no ports>");
38876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else {
38976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                for (PortInfo portInfo : mPorts.values()) {
39076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    pw.println("  " + portInfo.mUsbPort.getId() + ": " + portInfo);
39176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
39276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
39376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
39476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
39576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
39676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void updatePortsLocked(IndentingPrintWriter pw) {
39776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Assume all ports are gone unless informed otherwise.
39876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Kind of pessimistic but simple.
39976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        for (int i = mPorts.size(); i-- > 0; ) {
40076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED;
40176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
40276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
40376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Enumerate all extant ports.
40476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (!mSimulatedPorts.isEmpty()) {
40576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final int count = mSimulatedPorts.size();
40676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            for (int i = 0; i < count; i++) {
40776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                final SimulatedPortInfo portInfo = mSimulatedPorts.valueAt(i);
40876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                addOrUpdatePortLocked(portInfo.mPortId, portInfo.mSupportedModes,
40976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        portInfo.mCurrentMode, portInfo.mCanChangeMode,
41076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        portInfo.mCurrentPowerRole, portInfo.mCanChangePowerRole,
41176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        portInfo.mCurrentDataRole, portInfo.mCanChangeDataRole, pw);
41276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
41376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        } else if (mHaveKernelSupport) {
41476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final File[] portDirs = new File(SYSFS_CLASS).listFiles();
41576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (portDirs != null) {
41676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                for (File portDir : portDirs) {
41776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    if (!portDir.isDirectory()) {
41876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        continue;
41976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    }
42076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
42176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    // Parse the sysfs file contents.
42276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final String portId = portDir.getName();
42376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final int supportedModes = readSupportedModes(portDir);
42476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final int currentMode = readCurrentMode(portDir);
42576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final boolean canChangeMode = canChangeMode(portDir);
42676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final int currentPowerRole = readCurrentPowerRole(portDir);
42776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final boolean canChangePowerRole = canChangePowerRole(portDir);
42876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final int currentDataRole = readCurrentDataRole(portDir);
42976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    final boolean canChangeDataRole = canChangeDataRole(portDir);
43076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    addOrUpdatePortLocked(portId, supportedModes,
43176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            currentMode, canChangeMode,
43276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            currentPowerRole, canChangePowerRole,
43376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            currentDataRole, canChangeDataRole, pw);
43476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                 }
43576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
43676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
43776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
43876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Process the updates.
43976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Once finished, the list of ports will only contain ports in DISPOSITION_READY.
44076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        for (int i = mPorts.size(); i-- > 0; ) {
44176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            final PortInfo portInfo = mPorts.valueAt(i);
44276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            switch (portInfo.mDisposition) {
44376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                case PortInfo.DISPOSITION_ADDED:
44476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    handlePortAddedLocked(portInfo, pw);
44576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    portInfo.mDisposition = PortInfo.DISPOSITION_READY;
44676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    break;
44776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                case PortInfo.DISPOSITION_CHANGED:
44876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    handlePortChangedLocked(portInfo, pw);
44976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    portInfo.mDisposition = PortInfo.DISPOSITION_READY;
45076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    break;
45176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                case PortInfo.DISPOSITION_REMOVED:
45276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    mPorts.removeAt(i);
45376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    portInfo.mUsbPortStatus = null; // must do this early
45476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    handlePortRemovedLocked(portInfo, pw);
45576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    break;
45676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
45776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
45876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
45976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
46076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    // Must only be called by updatePortsLocked.
46176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void addOrUpdatePortLocked(String portId, int supportedModes,
46276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            int currentMode, boolean canChangeMode,
46376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            int currentPowerRole, boolean canChangePowerRole,
46476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            int currentDataRole, boolean canChangeDataRole,
46576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            IndentingPrintWriter pw) {
46676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Only allow mode switch capability for dual role ports.
46776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Validate that the current mode matches the supported modes we expect.
46876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (supportedModes != UsbPort.MODE_DUAL) {
46976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            canChangeMode = false;
47076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (currentMode != 0 && currentMode != supportedModes) {
47176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
47276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
47376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + ", currentMode=" + UsbPort.modeToString(currentMode));
47476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                currentMode = 0;
47576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
47676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
47776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
47876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Determine the supported role combinations.
47976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Note that the policy is designed to prefer setting the power and data
48076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // role independently rather than changing the mode.
48176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        int supportedRoleCombinations = UsbPort.combineRolesAsBit(
48276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                currentPowerRole, currentDataRole);
48376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (currentMode != 0 && currentPowerRole != 0 && currentDataRole != 0) {
48476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (canChangePowerRole && canChangeDataRole) {
48576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Can change both power and data role independently.
48676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Assume all combinations are possible.
48776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                supportedRoleCombinations |=
48876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
48976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
49076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else if (canChangePowerRole) {
49176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Can only change power role.
49276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Assume data role must remain at its current value.
49376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
49476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        UsbPort.POWER_ROLE_SOURCE, currentDataRole);
49576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
49676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        UsbPort.POWER_ROLE_SINK, currentDataRole);
49776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else if (canChangeDataRole) {
49876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Can only change data role.
49976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Assume power role must remain at its current value.
50076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
50176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        currentPowerRole, UsbPort.DATA_ROLE_HOST);
50276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                supportedRoleCombinations |= UsbPort.combineRolesAsBit(
50376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        currentPowerRole, UsbPort.DATA_ROLE_DEVICE);
50476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else if (canChangeMode) {
50576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Can only change the mode.
50676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // Assume both standard UFP and DFP configurations will become available
50776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                // when this happens.
50876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                supportedRoleCombinations |= COMBO_SOURCE_HOST | COMBO_SINK_DEVICE;
50976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
51076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
51176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
51276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Update the port data structures.
51376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        PortInfo portInfo = mPorts.get(portId);
51476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (portInfo == null) {
51576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo = new PortInfo(portId, supportedModes);
51676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            portInfo.setStatus(currentMode, canChangeMode,
51776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    currentPowerRole, canChangePowerRole,
51876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    currentDataRole, canChangeDataRole,
51976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    supportedRoleCombinations);
52076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mPorts.put(portId, portInfo);
52176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        } else {
52276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            // Sanity check that ports aren't changing definition out from under us.
52376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (supportedModes != portInfo.mUsbPort.getSupportedModes()) {
52476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from "
52576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + "USB port driver (should be immutable): "
52676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + "previous=" + UsbPort.modeToString(
52776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                                portInfo.mUsbPort.getSupportedModes())
52876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        + ", current=" + UsbPort.modeToString(supportedModes));
52976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
53076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
53176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (portInfo.setStatus(currentMode, canChangeMode,
53276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    currentPowerRole, canChangePowerRole,
53376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    currentDataRole, canChangeDataRole,
53476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    supportedRoleCombinations)) {
53576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
53676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            } else {
53776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                portInfo.mDisposition = PortInfo.DISPOSITION_READY;
53876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
53976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
54076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
54176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
54276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
54376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        logAndPrint(Log.INFO, pw, "USB port added: " + portInfo);
54476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        sendPortChangedBroadcastLocked(portInfo);
54576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
54676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
54776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
54876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
54976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        sendPortChangedBroadcastLocked(portInfo);
55076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
55176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
55276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
55376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
55476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        sendPortChangedBroadcastLocked(portInfo);
55576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
55676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
55776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
55876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);
55976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
56076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort);
56176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
56276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
56376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // Guard against possible reentrance by posting the broadcast from the handler
56476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        // instead of from within the critical section.
56576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        mHandler.post(new Runnable() {
56676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            @Override
56776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            public void run() {
56876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
56976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
57076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        });
57176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
57276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
57376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private void scheduleUpdatePorts() {
57476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (!mHandler.hasMessages(MSG_UPDATE_PORTS)) {
57576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mHandler.sendEmptyMessage(MSG_UPDATE_PORTS);
57676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
57776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
57876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
57976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static int readSupportedModes(File portDir) {
58076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        int modes = 0;
58176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        final String contents = readFile(portDir, SYSFS_PORT_SUPPORTED_MODES);
58276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (contents != null) {
58376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.contains(PORT_MODE_DFP)) {
58476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                modes |= UsbPort.MODE_DFP;
58576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
58676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.contains(PORT_MODE_UFP)) {
58776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                modes |= UsbPort.MODE_UFP;
58876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
58976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
59076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        return modes;
59176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
59276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
59376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static int readCurrentMode(File portDir) {
59476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        final String contents = readFile(portDir, SYSFS_PORT_MODE);
59576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (contents != null) {
59676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.equals(PORT_MODE_DFP)) {
59776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return UsbPort.MODE_DFP;
59876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
59976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.equals(PORT_MODE_UFP)) {
60076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return UsbPort.MODE_UFP;
60176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
60276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
60376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        return 0;
60476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
60576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
60676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static int readCurrentPowerRole(File portDir) {
60776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        final String contents = readFile(portDir, SYSFS_PORT_POWER_ROLE);
60876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (contents != null) {
60976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.equals(PORT_POWER_ROLE_SOURCE)) {
61076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return UsbPort.POWER_ROLE_SOURCE;
61176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
61276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.equals(PORT_POWER_ROLE_SINK)) {
61376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return UsbPort.POWER_ROLE_SINK;
61476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
61576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
61676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        return 0;
61776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
61876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
61976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static int readCurrentDataRole(File portDir) {
62076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        final String contents = readFile(portDir, SYSFS_PORT_DATA_ROLE);
62176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (contents != null) {
62276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.equals(PORT_DATA_ROLE_HOST)) {
62376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return UsbPort.DATA_ROLE_HOST;
62476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
62576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (contents.equals(PORT_DATA_ROLE_DEVICE)) {
62676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return UsbPort.DATA_ROLE_DEVICE;
62776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
62876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
62976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        return 0;
63076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
63176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
6326ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    private static boolean fileIsRootWritable(String path) {
6336ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        try {
6346ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            // If the file is user writable, then it is root writable.
6356ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            return (Os.stat(path).st_mode & OsConstants.S_IWUSR) != 0;
6366ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        } catch (ErrnoException e) {
6376ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            return false;
6386ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        }
6396ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    }
6406ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn
64176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static boolean canChangeMode(File portDir) {
6426ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        return fileIsRootWritable(new File(portDir, SYSFS_PORT_MODE).getPath());
64376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
64476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
64576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static boolean canChangePowerRole(File portDir) {
6466ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        return fileIsRootWritable(new File(portDir, SYSFS_PORT_POWER_ROLE).getPath());
64776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
64876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
64976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static boolean canChangeDataRole(File portDir) {
6506ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        return fileIsRootWritable(new File(portDir, SYSFS_PORT_DATA_ROLE).getPath());
65176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
65276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
65376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static String readFile(File dir, String filename) {
65476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        final File file = new File(dir, filename);
65576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        try {
65676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            return IoUtils.readFileAsString(file.getAbsolutePath()).trim();
65776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        } catch (IOException ex) {
65876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            return null;
65976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
66076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
66176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
6626ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    private static boolean waitForState(String property, String state) {
6636ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        // wait for the transition to complete.
6646ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        // give up after 5 seconds.
6656ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        // 5 seconds is probably too long, but we have seen hardware that takes
6666ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        // over 3 seconds to change states.
6676ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        String value = null;
6686ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        for (int i = 0; i < 100; i++) {
6696ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            // State transition is done when property is set to the new configuration
6706ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            value = SystemProperties.get(property);
6716ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            if (state.equals(value)) return true;
6726ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn            SystemClock.sleep(50);
67376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
6746ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        Slog.e(TAG, "waitForState(" + state + ") for " + property + " FAILED: got " + value);
6756ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        return false;
6766ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    }
6776ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn
6786ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    private static String propertyFromFilename(String filename) {
6796ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        return USB_TYPEC_PROP_PREFIX + filename;
6806ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    }
6816ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn
6826ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn    private static boolean writeFile(File dir, String filename, String contents) {
6836ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        SystemProperties.set(propertyFromFilename(filename), contents);
6846ea47a3542ad5fd79530cfd5d1e3b30c3d52f722Tim Kilbourn        return waitForState(USB_TYPEC_STATE, contents);
68576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
68676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
68776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
68876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        Slog.println(priority, TAG, msg);
68976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        if (pw != null) {
69076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            pw.println(msg);
69176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
69276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
69376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
69476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final Handler mHandler = new Handler(FgThread.get().getLooper()) {
69576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        @Override
69676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public void handleMessage(Message msg) {
69776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            switch (msg.what) {
69876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                case MSG_UPDATE_PORTS: {
69976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    synchronized (mLock) {
70076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        updatePortsLocked(null);
70176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    }
70276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    break;
70376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                }
70476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
70576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
70676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    };
70776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
70876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private final UEventObserver mUEventObserver = new UEventObserver() {
70976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        @Override
71076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public void onUEvent(UEvent event) {
71176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            scheduleUpdatePorts();
71276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
71376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    };
71476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
71576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    /**
71676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown     * Describes a USB port.
71776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown     */
71876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final class PortInfo {
71976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public static final int DISPOSITION_ADDED = 0;
72076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public static final int DISPOSITION_CHANGED = 1;
72176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public static final int DISPOSITION_READY = 2;
72276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public static final int DISPOSITION_REMOVED = 3;
72376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
72476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public final UsbPort mUsbPort;
72576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public UsbPortStatus mUsbPortStatus;
72676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean mCanChangeMode;
72776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean mCanChangePowerRole;
72876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean mCanChangeDataRole;
72976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public int mDisposition; // default initialized to 0 which means added
73076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
73176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public PortInfo(String portId, int supportedModes) {
73276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mUsbPort = new UsbPort(portId, supportedModes);
73376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
73476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
73576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean setStatus(int currentMode, boolean canChangeMode,
73676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                int currentPowerRole, boolean canChangePowerRole,
73776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                int currentDataRole, boolean canChangeDataRole,
73876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                int supportedRoleCombinations) {
73976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mCanChangeMode = canChangeMode;
74076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mCanChangePowerRole = canChangePowerRole;
74176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mCanChangeDataRole = canChangeDataRole;
74276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            if (mUsbPortStatus == null
74376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    || mUsbPortStatus.getCurrentMode() != currentMode
74476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
74576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    || mUsbPortStatus.getCurrentDataRole() != currentDataRole
74676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    || mUsbPortStatus.getSupportedRoleCombinations()
74776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                            != supportedRoleCombinations) {
74876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
74976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                        supportedRoleCombinations);
75076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                return true;
75176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            }
75276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            return false;
75376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
75476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
75576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        @Override
75676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public String toString() {
75776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            return "port=" + mUsbPort + ", status=" + mUsbPortStatus
75876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", canChangeMode=" + mCanChangeMode
75976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", canChangePowerRole=" + mCanChangePowerRole
76076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown                    + ", canChangeDataRole=" + mCanChangeDataRole;
76176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
76276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
76376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
76476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    /**
76576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown     * Describes a simulated USB port.
76676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown     * Roughly mirrors the information we would ordinarily get from the kernel.
76776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown     */
76876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    private static final class SimulatedPortInfo {
76976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public final String mPortId;
77076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public final int mSupportedModes;
77176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public int mCurrentMode;
77276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean mCanChangeMode;
77376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public int mCurrentPowerRole;
77476c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean mCanChangePowerRole;
77576c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public int mCurrentDataRole;
77676c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public boolean mCanChangeDataRole;
77776c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown
77876c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        public SimulatedPortInfo(String portId, int supportedModes) {
77976c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mPortId = portId;
78076c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown            mSupportedModes = supportedModes;
78176c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown        }
78276c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown    }
78376c4c6668a1486bc003ab0c585bb1f41d16e27a7Jeff Brown}
784