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