• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.usb;
18 
19 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
20 import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
21 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
22 import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
23 import static android.hardware.usb.UsbPortStatus.MODE_DFP;
24 import static android.hardware.usb.UsbPortStatus.MODE_DUAL;
25 import static android.hardware.usb.UsbPortStatus.MODE_UFP;
26 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
27 import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
28 
29 import static com.android.internal.usb.DumpUtils.writePort;
30 import static com.android.internal.usb.DumpUtils.writePortStatus;
31 
32 import android.Manifest;
33 import android.annotation.NonNull;
34 import android.app.Notification;
35 import android.app.NotificationManager;
36 import android.app.PendingIntent;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.res.Resources;
40 import android.hardware.usb.ParcelableUsbPort;
41 import android.hardware.usb.UsbManager;
42 import android.hardware.usb.UsbPort;
43 import android.hardware.usb.UsbPortStatus;
44 import android.hardware.usb.V1_0.IUsb;
45 import android.hardware.usb.V1_0.PortRole;
46 import android.hardware.usb.V1_0.PortRoleType;
47 import android.hardware.usb.V1_0.Status;
48 import android.hardware.usb.V1_1.PortStatus_1_1;
49 import android.hardware.usb.V1_2.IUsbCallback;
50 import android.hardware.usb.V1_2.PortStatus;
51 import android.hidl.manager.V1_0.IServiceManager;
52 import android.hidl.manager.V1_0.IServiceNotification;
53 import android.os.Bundle;
54 import android.os.Handler;
55 import android.os.HwBinder;
56 import android.os.Message;
57 import android.os.Parcel;
58 import android.os.Parcelable;
59 import android.os.RemoteException;
60 import android.os.SystemClock;
61 import android.os.UserHandle;
62 import android.service.usb.UsbPortInfoProto;
63 import android.service.usb.UsbPortManagerProto;
64 import android.service.usb.UsbServiceProto;
65 import android.util.ArrayMap;
66 import android.util.Log;
67 import android.util.Slog;
68 import android.util.StatsLog;
69 
70 import com.android.internal.annotations.GuardedBy;
71 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
72 import com.android.internal.notification.SystemNotificationChannels;
73 import com.android.internal.util.IndentingPrintWriter;
74 import com.android.internal.util.dump.DualDumpOutputStream;
75 import com.android.server.FgThread;
76 
77 import java.util.ArrayList;
78 import java.util.NoSuchElementException;
79 
80 /**
81  * Allows trusted components to control the properties of physical USB ports
82  * via the IUsb.hal.
83  * <p>
84  * Note: This interface may not be supported on all chipsets since the USB drivers
85  * must be changed to publish this information through the module.  At the moment
86  * we only need this for devices with USB Type C ports to allow the System UI to
87  * control USB charging and data direction.  On devices that do not support this
88  * interface the list of ports may incorrectly appear to be empty
89  * (but we don't care today).
90  * </p>
91  */
92 public class UsbPortManager {
93     private static final String TAG = "UsbPortManager";
94 
95     private static final int MSG_UPDATE_PORTS = 1;
96     private static final int MSG_SYSTEM_READY = 2;
97 
98     // All non-trivial role combinations.
99     private static final int COMBO_SOURCE_HOST =
100             UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_HOST);
101     private static final int COMBO_SOURCE_DEVICE = UsbPort.combineRolesAsBit(
102             POWER_ROLE_SOURCE, DATA_ROLE_DEVICE);
103     private static final int COMBO_SINK_HOST =
104             UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_HOST);
105     private static final int COMBO_SINK_DEVICE = UsbPort.combineRolesAsBit(
106             POWER_ROLE_SINK, DATA_ROLE_DEVICE);
107 
108     // The system context.
109     private final Context mContext;
110 
111     // Proxy object for the usb hal daemon.
112     @GuardedBy("mLock")
113     private IUsb mProxy = null;
114 
115     // Callback when the UsbPort status is changed by the kernel.
116     // Mostly due a command sent by the remote Usb device.
117     private HALCallback mHALCallback = new HALCallback(null, this);
118 
119     // Cookie sent for usb hal death notification.
120     private static final int USB_HAL_DEATH_COOKIE = 1000;
121 
122     // Used as the key while sending the bundle to Main thread.
123     private static final String PORT_INFO = "port_info";
124 
125     // This is monitored to prevent updating the protInfo before the system
126     // is ready.
127     private boolean mSystemReady;
128 
129     // Mutex for all mutable shared state.
130     private final Object mLock = new Object();
131 
132     // List of all ports, indexed by id.
133     // Ports may temporarily have different dispositions as they are added or removed
134     // but the class invariant is that this list will only contain ports with DISPOSITION_READY
135     // except while updatePortsLocked() is in progress.
136     private final ArrayMap<String, PortInfo> mPorts = new ArrayMap<>();
137 
138     // List of all simulated ports, indexed by id.
139     private final ArrayMap<String, RawPortInfo> mSimulatedPorts =
140             new ArrayMap<>();
141 
142     // Maintains the current connected status of the port.
143     // Uploads logs only when the connection status is changes.
144     private final ArrayMap<String, Boolean> mConnected = new ArrayMap<>();
145 
146     // Maintains the USB contaminant status that was previously logged.
147     // Logs get uploaded only when contaminant presence status changes.
148     private final ArrayMap<String, Integer> mContaminantStatus = new ArrayMap<>();
149 
150     private NotificationManager mNotificationManager;
151 
152     /**
153      * If there currently is a notification related to contaminated USB port management
154      * shown the id of the notification, or 0 if there is none.
155      */
156     private int mIsPortContaminatedNotificationId;
157 
UsbPortManager(Context context)158     public UsbPortManager(Context context) {
159         mContext = context;
160         try {
161             ServiceNotification serviceNotification = new ServiceNotification();
162 
163             boolean ret = IServiceManager.getService()
164                     .registerForNotifications("android.hardware.usb@1.0::IUsb",
165                             "", serviceNotification);
166             if (!ret) {
167                 logAndPrint(Log.ERROR, null,
168                         "Failed to register service start notification");
169             }
170         } catch (RemoteException e) {
171             logAndPrintException(null,
172                     "Failed to register service start notification", e);
173             return;
174         }
175         connectToProxy(null);
176     }
177 
systemReady()178     public void systemReady() {
179 	mSystemReady = true;
180         if (mProxy != null) {
181             try {
182                 mProxy.queryPortStatus();
183             } catch (RemoteException e) {
184                 logAndPrintException(null,
185                         "ServiceStart: Failed to query port status", e);
186             }
187         }
188         mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
189     }
190 
updateContaminantNotification()191     private void updateContaminantNotification() {
192         PortInfo currentPortInfo = null;
193         Resources r = mContext.getResources();
194         int contaminantStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
195 
196         // Not handling multiple ports here. Showing the notification
197         // for the first port that returns CONTAMINANT_PRESENCE_DETECTED.
198         for (PortInfo portInfo : mPorts.values()) {
199             contaminantStatus = portInfo.mUsbPortStatus.getContaminantDetectionStatus();
200             if (contaminantStatus == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED
201                     || contaminantStatus == UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) {
202                 currentPortInfo = portInfo;
203                 break;
204             }
205         }
206 
207         // Current contminant status is detected while "safe to use usb port"
208         // notification is displayed. Remove safe to use usb port notification
209         // and push contaminant detected notification.
210         if (contaminantStatus == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED
211                     && mIsPortContaminatedNotificationId
212                     != SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) {
213             if (mIsPortContaminatedNotificationId
214                     == SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED) {
215                 mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId,
216                         UserHandle.ALL);
217             }
218 
219             mIsPortContaminatedNotificationId = SystemMessage.NOTE_USB_CONTAMINANT_DETECTED;
220             int titleRes = com.android.internal.R.string.usb_contaminant_detected_title;
221             CharSequence title = r.getText(titleRes);
222             String channel = SystemNotificationChannels.ALERTS;
223             CharSequence message = r.getText(
224                     com.android.internal.R.string.usb_contaminant_detected_message);
225 
226             Intent intent = new Intent();
227             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
228             intent.setClassName("com.android.systemui",
229                     "com.android.systemui.usb.UsbContaminantActivity");
230             intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(currentPortInfo.mUsbPort));
231 
232             PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
233                                 intent, 0, null, UserHandle.CURRENT);
234 
235             Notification.Builder builder = new Notification.Builder(mContext, channel)
236                     .setOngoing(true)
237                     .setTicker(title)
238                     .setColor(mContext.getColor(
239                            com.android.internal.R.color
240                            .system_notification_accent_color))
241                     .setContentIntent(pi)
242                     .setContentTitle(title)
243                     .setContentText(message)
244                     .setVisibility(Notification.VISIBILITY_PUBLIC)
245                     .setSmallIcon(android.R.drawable.stat_sys_warning)
246                     .setStyle(new Notification.BigTextStyle()
247                     .bigText(message));
248             Notification notification = builder.build();
249             mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId, notification,
250                     UserHandle.ALL);
251         // No contaminant is detected but contaminant detection notification is displayed.
252         // Remove contaminant detection notification and push safe to use USB port notification.
253         } else if (contaminantStatus != UsbPortStatus.CONTAMINANT_DETECTION_DETECTED
254                 && mIsPortContaminatedNotificationId
255                 == SystemMessage.NOTE_USB_CONTAMINANT_DETECTED) {
256             mNotificationManager.cancelAsUser(null, mIsPortContaminatedNotificationId,
257                     UserHandle.ALL);
258             mIsPortContaminatedNotificationId = 0;
259 
260             // Dont show safe to use notification when contaminant detection is disabled.
261             // Show only when the status is changing from detected to not detected.
262             if (contaminantStatus == UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED) {
263                 mIsPortContaminatedNotificationId =
264                         SystemMessage.NOTE_USB_CONTAMINANT_NOT_DETECTED;
265                 int titleRes = com.android.internal.R.string.usb_contaminant_not_detected_title;
266                 CharSequence title = r.getText(titleRes);
267                 String channel = SystemNotificationChannels.ALERTS;
268                 CharSequence message = r.getText(
269                         com.android.internal.R.string.usb_contaminant_not_detected_message);
270 
271                 Notification.Builder builder = new Notification.Builder(mContext, channel)
272                         .setSmallIcon(com.android.internal.R.drawable.ic_usb_48dp)
273                         .setTicker(title)
274                         .setColor(mContext.getColor(
275                                com.android.internal.R.color
276                                .system_notification_accent_color))
277                         .setContentTitle(title)
278                         .setContentText(message)
279                         .setVisibility(Notification.VISIBILITY_PUBLIC)
280                         .setStyle(new Notification.BigTextStyle()
281                         .bigText(message));
282                 Notification notification = builder.build();
283                 mNotificationManager.notifyAsUser(null, mIsPortContaminatedNotificationId,
284                         notification, UserHandle.ALL);
285             }
286         }
287     }
288 
getPorts()289     public UsbPort[] getPorts() {
290         synchronized (mLock) {
291             final int count = mPorts.size();
292             final UsbPort[] result = new UsbPort[count];
293             for (int i = 0; i < count; i++) {
294                 result[i] = mPorts.valueAt(i).mUsbPort;
295             }
296             return result;
297         }
298     }
299 
getPortStatus(String portId)300     public UsbPortStatus getPortStatus(String portId) {
301         synchronized (mLock) {
302             final PortInfo portInfo = mPorts.get(portId);
303             return portInfo != null ? portInfo.mUsbPortStatus : null;
304         }
305     }
306 
307     /**
308      * Enables/disables contaminant detection.
309      *
310      * @param portId port identifier.
311      * @param enable enable contaminant detection when set to true.
312      */
enableContaminantDetection(@onNull String portId, boolean enable, @NonNull IndentingPrintWriter pw)313     public void enableContaminantDetection(@NonNull String portId, boolean enable,
314             @NonNull IndentingPrintWriter pw) {
315         final PortInfo portInfo = mPorts.get(portId);
316         if (portInfo == null) {
317             if (pw != null) {
318                 pw.println("No such USB port: " + portId);
319             }
320             return;
321         }
322 
323         if (!portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) {
324             return;
325         }
326 
327         if ((enable && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
328                 != UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) || (!enable
329                 && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
330                 == UsbPortStatus.CONTAMINANT_DETECTION_DISABLED)
331                 || (portInfo.mUsbPortStatus.getContaminantDetectionStatus()
332                 == UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED)) {
333             return;
334         }
335 
336         try {
337             // Oneway call into the hal. Use the castFrom method from HIDL.
338             android.hardware.usb.V1_2.IUsb proxy = android.hardware.usb.V1_2.IUsb.castFrom(mProxy);
339             proxy.enableContaminantPresenceDetection(portId, enable);
340         } catch (RemoteException e) {
341             logAndPrintException(pw, "Failed to set contaminant detection", e);
342         } catch (ClassCastException e) {
343             logAndPrintException(pw, "Method only applicable to V1.2 or above implementation", e);
344         }
345     }
346 
setPortRoles(String portId, int newPowerRole, int newDataRole, IndentingPrintWriter pw)347     public void setPortRoles(String portId, int newPowerRole, int newDataRole,
348             IndentingPrintWriter pw) {
349         synchronized (mLock) {
350             final PortInfo portInfo = mPorts.get(portId);
351             if (portInfo == null) {
352                 if (pw != null) {
353                     pw.println("No such USB port: " + portId);
354                 }
355                 return;
356             }
357 
358             // Check whether the new role is actually supported.
359             if (!portInfo.mUsbPortStatus.isRoleCombinationSupported(newPowerRole, newDataRole)) {
360                 logAndPrint(Log.ERROR, pw, "Attempted to set USB port into unsupported "
361                         + "role combination: portId=" + portId
362                         + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
363                         + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
364                 return;
365             }
366 
367             // Check whether anything actually changed.
368             final int currentDataRole = portInfo.mUsbPortStatus.getCurrentDataRole();
369             final int currentPowerRole = portInfo.mUsbPortStatus.getCurrentPowerRole();
370             if (currentDataRole == newDataRole && currentPowerRole == newPowerRole) {
371                 if (pw != null) {
372                     pw.println("No change.");
373                 }
374                 return;
375             }
376 
377             // Determine whether we need to change the mode in order to accomplish this goal.
378             // We prefer not to do this since it's more likely to fail.
379             //
380             // Note: Arguably it might be worth allowing the client to influence this policy
381             // decision so that we could show more powerful developer facing UI but let's
382             // see how far we can get without having to do that.
383             final boolean canChangeMode = portInfo.mCanChangeMode;
384             final boolean canChangePowerRole = portInfo.mCanChangePowerRole;
385             final boolean canChangeDataRole = portInfo.mCanChangeDataRole;
386             final int currentMode = portInfo.mUsbPortStatus.getCurrentMode();
387             final int newMode;
388             if ((!canChangePowerRole && currentPowerRole != newPowerRole)
389                     || (!canChangeDataRole && currentDataRole != newDataRole)) {
390                 if (canChangeMode && newPowerRole == POWER_ROLE_SOURCE
391                         && newDataRole == DATA_ROLE_HOST) {
392                     newMode = MODE_DFP;
393                 } else if (canChangeMode && newPowerRole == POWER_ROLE_SINK
394                         && newDataRole == DATA_ROLE_DEVICE) {
395                     newMode = MODE_UFP;
396                 } else {
397                     logAndPrint(Log.ERROR, pw, "Found mismatch in supported USB role combinations "
398                             + "while attempting to change role: " + portInfo
399                             + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
400                             + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
401                     return;
402                 }
403             } else {
404                 newMode = currentMode;
405             }
406 
407             // Make it happen.
408             logAndPrint(Log.INFO, pw, "Setting USB port mode and role: portId=" + portId
409                     + ", currentMode=" + UsbPort.modeToString(currentMode)
410                     + ", currentPowerRole=" + UsbPort.powerRoleToString(currentPowerRole)
411                     + ", currentDataRole=" + UsbPort.dataRoleToString(currentDataRole)
412                     + ", newMode=" + UsbPort.modeToString(newMode)
413                     + ", newPowerRole=" + UsbPort.powerRoleToString(newPowerRole)
414                     + ", newDataRole=" + UsbPort.dataRoleToString(newDataRole));
415 
416             RawPortInfo sim = mSimulatedPorts.get(portId);
417             if (sim != null) {
418                 // Change simulated state.
419                 sim.currentMode = newMode;
420                 sim.currentPowerRole = newPowerRole;
421                 sim.currentDataRole = newDataRole;
422                 updatePortsLocked(pw, null);
423             } else if (mProxy != null) {
424                 if (currentMode != newMode) {
425                     // Changing the mode will have the side-effect of also changing
426                     // the power and data roles but it might take some time to apply
427                     // and the renegotiation might fail.  Due to limitations of the USB
428                     // hardware, we have no way of knowing whether it will work apriori
429                     // which is why we would prefer to set the power and data roles
430                     // directly instead.
431 
432                     logAndPrint(Log.ERROR, pw, "Trying to set the USB port mode: "
433                             + "portId=" + portId
434                             + ", newMode=" + UsbPort.modeToString(newMode));
435                     PortRole newRole = new PortRole();
436                     newRole.type = PortRoleType.MODE;
437                     newRole.role = newMode;
438                     try {
439                         mProxy.switchRole(portId, newRole);
440                     } catch (RemoteException e) {
441                         logAndPrintException(pw, "Failed to set the USB port mode: "
442                                 + "portId=" + portId
443                                 + ", newMode=" + UsbPort.modeToString(newRole.role), e);
444                     }
445                 } else {
446                     // Change power and data role independently as needed.
447                     if (currentPowerRole != newPowerRole) {
448                         PortRole newRole = new PortRole();
449                         newRole.type = PortRoleType.POWER_ROLE;
450                         newRole.role = newPowerRole;
451                         try {
452                             mProxy.switchRole(portId, newRole);
453                         } catch (RemoteException e) {
454                             logAndPrintException(pw, "Failed to set the USB port power role: "
455                                             + "portId=" + portId
456                                             + ", newPowerRole=" + UsbPort.powerRoleToString
457                                             (newRole.role),
458                                     e);
459                             return;
460                         }
461                     }
462                     if (currentDataRole != newDataRole) {
463                         PortRole newRole = new PortRole();
464                         newRole.type = PortRoleType.DATA_ROLE;
465                         newRole.role = newDataRole;
466                         try {
467                             mProxy.switchRole(portId, newRole);
468                         } catch (RemoteException e) {
469                             logAndPrintException(pw, "Failed to set the USB port data role: "
470                                             + "portId=" + portId
471                                             + ", newDataRole=" + UsbPort.dataRoleToString(newRole
472                                             .role),
473                                     e);
474                         }
475                     }
476                 }
477             }
478         }
479     }
480 
addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw)481     public void addSimulatedPort(String portId, int supportedModes, IndentingPrintWriter pw) {
482         synchronized (mLock) {
483             if (mSimulatedPorts.containsKey(portId)) {
484                 pw.println("Port with same name already exists.  Please remove it first.");
485                 return;
486             }
487 
488             pw.println("Adding simulated port: portId=" + portId
489                     + ", supportedModes=" + UsbPort.modeToString(supportedModes));
490             mSimulatedPorts.put(portId,
491                     new RawPortInfo(portId, supportedModes));
492             updatePortsLocked(pw, null);
493         }
494     }
495 
connectSimulatedPort(String portId, int mode, boolean canChangeMode, int powerRole, boolean canChangePowerRole, int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw)496     public void connectSimulatedPort(String portId, int mode, boolean canChangeMode,
497             int powerRole, boolean canChangePowerRole,
498             int dataRole, boolean canChangeDataRole, IndentingPrintWriter pw) {
499         synchronized (mLock) {
500             final RawPortInfo portInfo = mSimulatedPorts.get(portId);
501             if (portInfo == null) {
502                 pw.println("Cannot connect simulated port which does not exist.");
503                 return;
504             }
505 
506             if (mode == 0 || powerRole == 0 || dataRole == 0) {
507                 pw.println("Cannot connect simulated port in null mode, "
508                         + "power role, or data role.");
509                 return;
510             }
511 
512             if ((portInfo.supportedModes & mode) == 0) {
513                 pw.println("Simulated port does not support mode: " + UsbPort.modeToString(mode));
514                 return;
515             }
516 
517             pw.println("Connecting simulated port: portId=" + portId
518                     + ", mode=" + UsbPort.modeToString(mode)
519                     + ", canChangeMode=" + canChangeMode
520                     + ", powerRole=" + UsbPort.powerRoleToString(powerRole)
521                     + ", canChangePowerRole=" + canChangePowerRole
522                     + ", dataRole=" + UsbPort.dataRoleToString(dataRole)
523                     + ", canChangeDataRole=" + canChangeDataRole);
524             portInfo.currentMode = mode;
525             portInfo.canChangeMode = canChangeMode;
526             portInfo.currentPowerRole = powerRole;
527             portInfo.canChangePowerRole = canChangePowerRole;
528             portInfo.currentDataRole = dataRole;
529             portInfo.canChangeDataRole = canChangeDataRole;
530             updatePortsLocked(pw, null);
531         }
532     }
533 
534     /**
535      * Sets contaminant status for simulated USB port objects.
536      */
simulateContaminantStatus(String portId, boolean detected, IndentingPrintWriter pw)537     public void simulateContaminantStatus(String portId, boolean detected,
538             IndentingPrintWriter pw) {
539         synchronized (mLock) {
540             final RawPortInfo portInfo = mSimulatedPorts.get(portId);
541             if (portInfo == null) {
542                 pw.println("Simulated port not found.");
543                 return;
544             }
545 
546             pw.println("Simulating wet port: portId=" + portId
547                     + ", wet=" + detected);
548             portInfo.contaminantDetectionStatus = detected
549                     ? UsbPortStatus.CONTAMINANT_DETECTION_DETECTED
550                     : UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED;
551             updatePortsLocked(pw, null);
552         }
553     }
554 
disconnectSimulatedPort(String portId, IndentingPrintWriter pw)555     public void disconnectSimulatedPort(String portId, IndentingPrintWriter pw) {
556         synchronized (mLock) {
557             final RawPortInfo portInfo = mSimulatedPorts.get(portId);
558             if (portInfo == null) {
559                 pw.println("Cannot disconnect simulated port which does not exist.");
560                 return;
561             }
562 
563             pw.println("Disconnecting simulated port: portId=" + portId);
564             portInfo.currentMode = 0;
565             portInfo.canChangeMode = false;
566             portInfo.currentPowerRole = 0;
567             portInfo.canChangePowerRole = false;
568             portInfo.currentDataRole = 0;
569             portInfo.canChangeDataRole = false;
570             updatePortsLocked(pw, null);
571         }
572     }
573 
removeSimulatedPort(String portId, IndentingPrintWriter pw)574     public void removeSimulatedPort(String portId, IndentingPrintWriter pw) {
575         synchronized (mLock) {
576             final int index = mSimulatedPorts.indexOfKey(portId);
577             if (index < 0) {
578                 pw.println("Cannot remove simulated port which does not exist.");
579                 return;
580             }
581 
582             pw.println("Disconnecting simulated port: portId=" + portId);
583             mSimulatedPorts.removeAt(index);
584             updatePortsLocked(pw, null);
585         }
586     }
587 
resetSimulation(IndentingPrintWriter pw)588     public void resetSimulation(IndentingPrintWriter pw) {
589         synchronized (mLock) {
590             pw.println("Removing all simulated ports and ending simulation.");
591             if (!mSimulatedPorts.isEmpty()) {
592                 mSimulatedPorts.clear();
593                 updatePortsLocked(pw, null);
594             }
595         }
596     }
597 
598     /**
599      * Dump the USB port state.
600      */
dump(DualDumpOutputStream dump, String idName, long id)601     public void dump(DualDumpOutputStream dump, String idName, long id) {
602         long token = dump.start(idName, id);
603 
604         synchronized (mLock) {
605             dump.write("is_simulation_active", UsbPortManagerProto.IS_SIMULATION_ACTIVE,
606                     !mSimulatedPorts.isEmpty());
607 
608             for (PortInfo portInfo : mPorts.values()) {
609                 portInfo.dump(dump, "usb_ports", UsbPortManagerProto.USB_PORTS);
610             }
611         }
612 
613         dump.end(token);
614     }
615 
616     private static class HALCallback extends IUsbCallback.Stub {
617         public IndentingPrintWriter pw;
618         public UsbPortManager portManager;
619 
HALCallback(IndentingPrintWriter pw, UsbPortManager portManager)620         HALCallback(IndentingPrintWriter pw, UsbPortManager portManager) {
621             this.pw = pw;
622             this.portManager = portManager;
623         }
624 
notifyPortStatusChange( ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval)625         public void notifyPortStatusChange(
626                 ArrayList<android.hardware.usb.V1_0.PortStatus> currentPortStatus, int retval) {
627             if (!portManager.mSystemReady) {
628                 return;
629             }
630 
631             if (retval != Status.SUCCESS) {
632                 logAndPrint(Log.ERROR, pw, "port status enquiry failed");
633                 return;
634             }
635 
636             ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
637 
638             for (android.hardware.usb.V1_0.PortStatus current : currentPortStatus) {
639                 RawPortInfo temp = new RawPortInfo(current.portName,
640                         current.supportedModes, CONTAMINANT_PROTECTION_NONE,
641                         current.currentMode,
642                         current.canChangeMode, current.currentPowerRole,
643                         current.canChangePowerRole,
644                         current.currentDataRole, current.canChangeDataRole,
645                         false, CONTAMINANT_PROTECTION_NONE,
646                         false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
647                 newPortInfo.add(temp);
648                 logAndPrint(Log.INFO, pw, "ClientCallback V1_0: " + current.portName);
649             }
650 
651             Message message = portManager.mHandler.obtainMessage();
652             Bundle bundle = new Bundle();
653             bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
654             message.what = MSG_UPDATE_PORTS;
655             message.setData(bundle);
656             portManager.mHandler.sendMessage(message);
657         }
658 
659 
notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus, int retval)660         public void notifyPortStatusChange_1_1(ArrayList<PortStatus_1_1> currentPortStatus,
661                 int retval) {
662             if (!portManager.mSystemReady) {
663                 return;
664             }
665 
666             if (retval != Status.SUCCESS) {
667                 logAndPrint(Log.ERROR, pw, "port status enquiry failed");
668                 return;
669             }
670 
671             ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
672 
673             int numStatus = currentPortStatus.size();
674             for (int i = 0; i < numStatus; i++) {
675                 PortStatus_1_1 current = currentPortStatus.get(i);
676                 RawPortInfo temp = new RawPortInfo(current.status.portName,
677                         current.supportedModes, CONTAMINANT_PROTECTION_NONE,
678                         current.currentMode,
679                         current.status.canChangeMode, current.status.currentPowerRole,
680                         current.status.canChangePowerRole,
681                         current.status.currentDataRole, current.status.canChangeDataRole,
682                         false, CONTAMINANT_PROTECTION_NONE,
683                         false, CONTAMINANT_DETECTION_NOT_SUPPORTED);
684                 newPortInfo.add(temp);
685                 logAndPrint(Log.INFO, pw, "ClientCallback V1_1: " + current.status.portName);
686             }
687 
688             Message message = portManager.mHandler.obtainMessage();
689             Bundle bundle = new Bundle();
690             bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
691             message.what = MSG_UPDATE_PORTS;
692             message.setData(bundle);
693             portManager.mHandler.sendMessage(message);
694         }
695 
notifyPortStatusChange_1_2( ArrayList<PortStatus> currentPortStatus, int retval)696         public void notifyPortStatusChange_1_2(
697                 ArrayList<PortStatus> currentPortStatus, int retval) {
698             if (!portManager.mSystemReady) {
699                 return;
700             }
701 
702             if (retval != Status.SUCCESS) {
703                 logAndPrint(Log.ERROR, pw, "port status enquiry failed");
704                 return;
705             }
706 
707             ArrayList<RawPortInfo> newPortInfo = new ArrayList<>();
708 
709             int numStatus = currentPortStatus.size();
710             for (int i = 0; i < numStatus; i++) {
711                 PortStatus current = currentPortStatus.get(i);
712                 RawPortInfo temp = new RawPortInfo(current.status_1_1.status.portName,
713                         current.status_1_1.supportedModes,
714                         current.supportedContaminantProtectionModes,
715                         current.status_1_1.currentMode,
716                         current.status_1_1.status.canChangeMode,
717                         current.status_1_1.status.currentPowerRole,
718                         current.status_1_1.status.canChangePowerRole,
719                         current.status_1_1.status.currentDataRole,
720                         current.status_1_1.status.canChangeDataRole,
721                         current.supportsEnableContaminantPresenceProtection,
722                         current.contaminantProtectionStatus,
723                         current.supportsEnableContaminantPresenceDetection,
724                         current.contaminantDetectionStatus);
725                 newPortInfo.add(temp);
726                 logAndPrint(Log.INFO, pw, "ClientCallback V1_2: "
727                         + current.status_1_1.status.portName);
728             }
729 
730             Message message = portManager.mHandler.obtainMessage();
731             Bundle bundle = new Bundle();
732             bundle.putParcelableArrayList(PORT_INFO, newPortInfo);
733             message.what = MSG_UPDATE_PORTS;
734             message.setData(bundle);
735             portManager.mHandler.sendMessage(message);
736         }
737 
notifyRoleSwitchStatus(String portName, PortRole role, int retval)738         public void notifyRoleSwitchStatus(String portName, PortRole role, int retval) {
739             if (retval == Status.SUCCESS) {
740                 logAndPrint(Log.INFO, pw, portName + " role switch successful");
741             } else {
742                 logAndPrint(Log.ERROR, pw, portName + " role switch failed");
743             }
744         }
745     }
746 
747     final class DeathRecipient implements HwBinder.DeathRecipient {
748         public IndentingPrintWriter pw;
749 
DeathRecipient(IndentingPrintWriter pw)750         DeathRecipient(IndentingPrintWriter pw) {
751             this.pw = pw;
752         }
753 
754         @Override
serviceDied(long cookie)755         public void serviceDied(long cookie) {
756             if (cookie == USB_HAL_DEATH_COOKIE) {
757                 logAndPrint(Log.ERROR, pw, "Usb hal service died cookie: " + cookie);
758                 synchronized (mLock) {
759                     mProxy = null;
760                 }
761             }
762         }
763     }
764 
765     final class ServiceNotification extends IServiceNotification.Stub {
766         @Override
onRegistration(String fqName, String name, boolean preexisting)767         public void onRegistration(String fqName, String name, boolean preexisting) {
768             logAndPrint(Log.INFO, null, "Usb hal service started " + fqName + " " + name);
769             connectToProxy(null);
770         }
771     }
772 
connectToProxy(IndentingPrintWriter pw)773     private void connectToProxy(IndentingPrintWriter pw) {
774         synchronized (mLock) {
775             if (mProxy != null) {
776                 return;
777             }
778 
779             try {
780                 mProxy = IUsb.getService();
781                 mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);
782                 mProxy.setCallback(mHALCallback);
783                 mProxy.queryPortStatus();
784             } catch (NoSuchElementException e) {
785                 logAndPrintException(pw, "connectToProxy: usb hal service not found."
786                         + " Did the service fail to start?", e);
787             } catch (RemoteException e) {
788                 logAndPrintException(pw, "connectToProxy: usb hal service not responding", e);
789             }
790         }
791     }
792 
793     /**
794      * Simulated ports directly add the new roles to mSimulatedPorts before calling.
795      * USB hal callback populates and sends the newPortInfo.
796      */
updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo)797     private void updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo) {
798         for (int i = mPorts.size(); i-- > 0; ) {
799             mPorts.valueAt(i).mDisposition = PortInfo.DISPOSITION_REMOVED;
800         }
801 
802         // Enumerate all extant ports.
803         if (!mSimulatedPorts.isEmpty()) {
804             final int count = mSimulatedPorts.size();
805             for (int i = 0; i < count; i++) {
806                 final RawPortInfo portInfo = mSimulatedPorts.valueAt(i);
807                 addOrUpdatePortLocked(portInfo.portId, portInfo.supportedModes,
808                         portInfo.supportedContaminantProtectionModes,
809                         portInfo.currentMode, portInfo.canChangeMode,
810                         portInfo.currentPowerRole, portInfo.canChangePowerRole,
811                         portInfo.currentDataRole, portInfo.canChangeDataRole,
812                         portInfo.supportsEnableContaminantPresenceProtection,
813                         portInfo.contaminantProtectionStatus,
814                         portInfo.supportsEnableContaminantPresenceDetection,
815                         portInfo.contaminantDetectionStatus, pw);
816             }
817         } else {
818             for (RawPortInfo currentPortInfo : newPortInfo) {
819                 addOrUpdatePortLocked(currentPortInfo.portId, currentPortInfo.supportedModes,
820                         currentPortInfo.supportedContaminantProtectionModes,
821                         currentPortInfo.currentMode, currentPortInfo.canChangeMode,
822                         currentPortInfo.currentPowerRole, currentPortInfo.canChangePowerRole,
823                         currentPortInfo.currentDataRole, currentPortInfo.canChangeDataRole,
824                         currentPortInfo.supportsEnableContaminantPresenceProtection,
825                         currentPortInfo.contaminantProtectionStatus,
826                         currentPortInfo.supportsEnableContaminantPresenceDetection,
827                         currentPortInfo.contaminantDetectionStatus, pw);
828             }
829         }
830 
831         // Process the updates.
832         // Once finished, the list of ports will only contain ports in DISPOSITION_READY.
833         for (int i = mPorts.size(); i-- > 0; ) {
834             final PortInfo portInfo = mPorts.valueAt(i);
835             switch (portInfo.mDisposition) {
836                 case PortInfo.DISPOSITION_ADDED:
837                     handlePortAddedLocked(portInfo, pw);
838                     portInfo.mDisposition = PortInfo.DISPOSITION_READY;
839                     break;
840                 case PortInfo.DISPOSITION_CHANGED:
841                     handlePortChangedLocked(portInfo, pw);
842                     portInfo.mDisposition = PortInfo.DISPOSITION_READY;
843                     break;
844                 case PortInfo.DISPOSITION_REMOVED:
845                     mPorts.removeAt(i);
846                     portInfo.mUsbPortStatus = null; // must do this early
847                     handlePortRemovedLocked(portInfo, pw);
848                     break;
849             }
850         }
851     }
852 
853     // Must only be called by updatePortsLocked.
addOrUpdatePortLocked(String portId, int supportedModes, int supportedContaminantProtectionModes, int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, boolean supportsEnableContaminantPresenceProtection, int contaminantProtectionStatus, boolean supportsEnableContaminantPresenceDetection, int contaminantDetectionStatus, IndentingPrintWriter pw)854     private void addOrUpdatePortLocked(String portId, int supportedModes,
855             int supportedContaminantProtectionModes,
856             int currentMode, boolean canChangeMode,
857             int currentPowerRole, boolean canChangePowerRole,
858             int currentDataRole, boolean canChangeDataRole,
859             boolean supportsEnableContaminantPresenceProtection,
860             int contaminantProtectionStatus,
861             boolean supportsEnableContaminantPresenceDetection,
862             int contaminantDetectionStatus,
863             IndentingPrintWriter pw) {
864         // Only allow mode switch capability for dual role ports.
865         // Validate that the current mode matches the supported modes we expect.
866         if ((supportedModes & MODE_DUAL) != MODE_DUAL) {
867             canChangeMode = false;
868             if (currentMode != 0 && currentMode != supportedModes) {
869                 logAndPrint(Log.WARN, pw, "Ignoring inconsistent current mode from USB "
870                         + "port driver: supportedModes=" + UsbPort.modeToString(supportedModes)
871                         + ", currentMode=" + UsbPort.modeToString(currentMode));
872                 currentMode = 0;
873             }
874         }
875 
876         // Determine the supported role combinations.
877         // Note that the policy is designed to prefer setting the power and data
878         // role independently rather than changing the mode.
879         int supportedRoleCombinations = UsbPort.combineRolesAsBit(
880                 currentPowerRole, currentDataRole);
881         if (currentMode != 0 && currentPowerRole != 0 && currentDataRole != 0) {
882             if (canChangePowerRole && canChangeDataRole) {
883                 // Can change both power and data role independently.
884                 // Assume all combinations are possible.
885                 supportedRoleCombinations |=
886                         COMBO_SOURCE_HOST | COMBO_SOURCE_DEVICE
887                                 | COMBO_SINK_HOST | COMBO_SINK_DEVICE;
888             } else if (canChangePowerRole) {
889                 // Can only change power role.
890                 // Assume data role must remain at its current value.
891                 supportedRoleCombinations |= UsbPort.combineRolesAsBit(
892                         POWER_ROLE_SOURCE, currentDataRole);
893                 supportedRoleCombinations |= UsbPort.combineRolesAsBit(
894                         POWER_ROLE_SINK, currentDataRole);
895             } else if (canChangeDataRole) {
896                 // Can only change data role.
897                 // Assume power role must remain at its current value.
898                 supportedRoleCombinations |= UsbPort.combineRolesAsBit(
899                         currentPowerRole, DATA_ROLE_HOST);
900                 supportedRoleCombinations |= UsbPort.combineRolesAsBit(
901                         currentPowerRole, DATA_ROLE_DEVICE);
902             } else if (canChangeMode) {
903                 // Can only change the mode.
904                 // Assume both standard UFP and DFP configurations will become available
905                 // when this happens.
906                 supportedRoleCombinations |= COMBO_SOURCE_HOST | COMBO_SINK_DEVICE;
907             }
908         }
909 
910         // Update the port data structures.
911         PortInfo portInfo = mPorts.get(portId);
912         if (portInfo == null) {
913             portInfo = new PortInfo(mContext.getSystemService(UsbManager.class),
914                 portId, supportedModes, supportedContaminantProtectionModes,
915                 supportsEnableContaminantPresenceProtection,
916                 supportsEnableContaminantPresenceDetection);
917             portInfo.setStatus(currentMode, canChangeMode,
918                     currentPowerRole, canChangePowerRole,
919                     currentDataRole, canChangeDataRole,
920                     supportedRoleCombinations, contaminantProtectionStatus,
921                     contaminantDetectionStatus);
922             mPorts.put(portId, portInfo);
923         } else {
924             // Sanity check that ports aren't changing definition out from under us.
925             if (supportedModes != portInfo.mUsbPort.getSupportedModes()) {
926                 logAndPrint(Log.WARN, pw, "Ignoring inconsistent list of supported modes from "
927                         + "USB port driver (should be immutable): "
928                         + "previous=" + UsbPort.modeToString(
929                         portInfo.mUsbPort.getSupportedModes())
930                         + ", current=" + UsbPort.modeToString(supportedModes));
931             }
932 
933             if (supportsEnableContaminantPresenceProtection
934                     != portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()) {
935                 logAndPrint(Log.WARN, pw,
936                         "Ignoring inconsistent supportsEnableContaminantPresenceProtection"
937                         + "USB port driver (should be immutable): "
938                         + "previous="
939                         + portInfo.mUsbPort.supportsEnableContaminantPresenceProtection()
940                         + ", current=" + supportsEnableContaminantPresenceProtection);
941             }
942 
943             if (supportsEnableContaminantPresenceDetection
944                     != portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()) {
945                 logAndPrint(Log.WARN, pw,
946                         "Ignoring inconsistent supportsEnableContaminantPresenceDetection "
947                         + "USB port driver (should be immutable): "
948                         + "previous="
949                         + portInfo.mUsbPort.supportsEnableContaminantPresenceDetection()
950                         + ", current=" + supportsEnableContaminantPresenceDetection);
951             }
952 
953 
954             if (portInfo.setStatus(currentMode, canChangeMode,
955                     currentPowerRole, canChangePowerRole,
956                     currentDataRole, canChangeDataRole,
957                     supportedRoleCombinations, contaminantProtectionStatus,
958                     contaminantDetectionStatus)) {
959                 portInfo.mDisposition = PortInfo.DISPOSITION_CHANGED;
960             } else {
961                 portInfo.mDisposition = PortInfo.DISPOSITION_READY;
962             }
963         }
964     }
965 
handlePortLocked(PortInfo portInfo, IndentingPrintWriter pw)966     private void handlePortLocked(PortInfo portInfo, IndentingPrintWriter pw) {
967         sendPortChangedBroadcastLocked(portInfo);
968         logToStatsd(portInfo, pw);
969         updateContaminantNotification();
970     }
971 
handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw)972     private void handlePortAddedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
973         logAndPrint(Log.INFO, pw, "USB port added: " + portInfo);
974         handlePortLocked(portInfo, pw);
975     }
976 
handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw)977     private void handlePortChangedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
978         logAndPrint(Log.INFO, pw, "USB port changed: " + portInfo);
979         enableContaminantDetectionIfNeeded(portInfo, pw);
980         handlePortLocked(portInfo, pw);
981     }
982 
handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw)983     private void handlePortRemovedLocked(PortInfo portInfo, IndentingPrintWriter pw) {
984         logAndPrint(Log.INFO, pw, "USB port removed: " + portInfo);
985         handlePortLocked(portInfo, pw);
986     }
987 
988     // Constants have to be converted between USB HAL V1.2 ContaminantDetectionStatus
989     // to usb.proto as proto guidelines recommends 0 to be UNKNOWN/UNSUPPORTTED
990     // whereas HAL policy is against a loosely defined constant.
convertContaminantDetectionStatusToProto(int contaminantDetectionStatus)991     private static int convertContaminantDetectionStatusToProto(int contaminantDetectionStatus) {
992         switch (contaminantDetectionStatus) {
993             case UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED:
994                 return UsbServiceProto.CONTAMINANT_STATUS_NOT_SUPPORTED;
995             case UsbPortStatus.CONTAMINANT_DETECTION_DISABLED:
996                 return UsbServiceProto.CONTAMINANT_STATUS_DISABLED;
997             case UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED:
998                 return UsbServiceProto.CONTAMINANT_STATUS_NOT_DETECTED;
999             case UsbPortStatus.CONTAMINANT_DETECTION_DETECTED:
1000                 return UsbServiceProto.CONTAMINANT_STATUS_DETECTED;
1001             default:
1002                 return UsbServiceProto.CONTAMINANT_STATUS_UNKNOWN;
1003         }
1004     }
1005 
sendPortChangedBroadcastLocked(PortInfo portInfo)1006     private void sendPortChangedBroadcastLocked(PortInfo portInfo) {
1007         final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);
1008         intent.addFlags(
1009                 Intent.FLAG_RECEIVER_FOREGROUND |
1010                         Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
1011         intent.putExtra(UsbManager.EXTRA_PORT, ParcelableUsbPort.of(portInfo.mUsbPort));
1012         intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);
1013 
1014         // Guard against possible reentrance by posting the broadcast from the handler
1015         // instead of from within the critical section.
1016         mHandler.post(() -> mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
1017                 Manifest.permission.MANAGE_USB));
1018     }
1019 
enableContaminantDetectionIfNeeded(PortInfo portInfo, IndentingPrintWriter pw)1020     private void enableContaminantDetectionIfNeeded(PortInfo portInfo, IndentingPrintWriter pw) {
1021         if (!mConnected.containsKey(portInfo.mUsbPort.getId())) {
1022             return;
1023         }
1024 
1025         if (mConnected.get(portInfo.mUsbPort.getId())
1026                 && !portInfo.mUsbPortStatus.isConnected()
1027                 && portInfo.mUsbPortStatus.getContaminantDetectionStatus()
1028                 == UsbPortStatus.CONTAMINANT_DETECTION_DISABLED) {
1029             // Contaminant detection might have been temporarily disabled by the user
1030             // through SystemUI.
1031             // Re-enable contaminant detection when the accessory is unplugged.
1032             enableContaminantDetection(portInfo.mUsbPort.getId(), true, pw);
1033         }
1034     }
1035 
logToStatsd(PortInfo portInfo, IndentingPrintWriter pw)1036     private void logToStatsd(PortInfo portInfo, IndentingPrintWriter pw) {
1037         // Port is removed
1038         if (portInfo.mUsbPortStatus == null) {
1039             if (mConnected.containsKey(portInfo.mUsbPort.getId())) {
1040                 //Previous logged a connected. Set it to disconnected.
1041                 if (mConnected.get(portInfo.mUsbPort.getId())) {
1042                     StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED,
1043                             StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
1044                             portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
1045                 }
1046                 mConnected.remove(portInfo.mUsbPort.getId());
1047             }
1048 
1049             if (mContaminantStatus.containsKey(portInfo.mUsbPort.getId())) {
1050                 //Previous logged a contaminant detected. Set it to not detected.
1051                 if ((mContaminantStatus.get(portInfo.mUsbPort.getId())
1052                         == UsbPortStatus.CONTAMINANT_DETECTION_DETECTED)) {
1053                     StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
1054                             portInfo.mUsbPort.getId(),
1055                             convertContaminantDetectionStatusToProto(
1056                                     UsbPortStatus.CONTAMINANT_DETECTION_NOT_DETECTED));
1057                 }
1058                 mContaminantStatus.remove(portInfo.mUsbPort.getId());
1059             }
1060             return;
1061         }
1062 
1063         if (!mConnected.containsKey(portInfo.mUsbPort.getId())
1064                 || (mConnected.get(portInfo.mUsbPort.getId())
1065                 != portInfo.mUsbPortStatus.isConnected())) {
1066             mConnected.put(portInfo.mUsbPort.getId(), portInfo.mUsbPortStatus.isConnected());
1067             StatsLog.write(StatsLog.USB_CONNECTOR_STATE_CHANGED,
1068                     portInfo.mUsbPortStatus.isConnected()
1069                     ? StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_CONNECTED :
1070                     StatsLog.USB_CONNECTOR_STATE_CHANGED__STATE__STATE_DISCONNECTED,
1071                     portInfo.mUsbPort.getId(), portInfo.mLastConnectDurationMillis);
1072         }
1073 
1074         if (!mContaminantStatus.containsKey(portInfo.mUsbPort.getId())
1075                 || (mContaminantStatus.get(portInfo.mUsbPort.getId())
1076                 != portInfo.mUsbPortStatus.getContaminantDetectionStatus())) {
1077             mContaminantStatus.put(portInfo.mUsbPort.getId(),
1078                     portInfo.mUsbPortStatus.getContaminantDetectionStatus());
1079             StatsLog.write(StatsLog.USB_CONTAMINANT_REPORTED,
1080                     portInfo.mUsbPort.getId(),
1081                     convertContaminantDetectionStatusToProto(
1082                             portInfo.mUsbPortStatus.getContaminantDetectionStatus()));
1083         }
1084     }
1085 
logAndPrint(int priority, IndentingPrintWriter pw, String msg)1086     private static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
1087         Slog.println(priority, TAG, msg);
1088         if (pw != null) {
1089             pw.println(msg);
1090         }
1091     }
1092 
logAndPrintException(IndentingPrintWriter pw, String msg, Exception e)1093     private static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
1094         Slog.e(TAG, msg, e);
1095         if (pw != null) {
1096             pw.println(msg + e);
1097         }
1098     }
1099 
1100     private final Handler mHandler = new Handler(FgThread.get().getLooper()) {
1101         @Override
1102         public void handleMessage(Message msg) {
1103             switch (msg.what) {
1104                 case MSG_UPDATE_PORTS: {
1105                     Bundle b = msg.getData();
1106                     ArrayList<RawPortInfo> PortInfo = b.getParcelableArrayList(PORT_INFO);
1107                     synchronized (mLock) {
1108                         updatePortsLocked(null, PortInfo);
1109                     }
1110                     break;
1111                 }
1112                 case MSG_SYSTEM_READY: {
1113                     mNotificationManager = (NotificationManager)
1114                             mContext.getSystemService(Context.NOTIFICATION_SERVICE);
1115                     break;
1116                 }
1117             }
1118         }
1119     };
1120 
1121     /**
1122      * Describes a USB port.
1123      */
1124     private static final class PortInfo {
1125         public static final int DISPOSITION_ADDED = 0;
1126         public static final int DISPOSITION_CHANGED = 1;
1127         public static final int DISPOSITION_READY = 2;
1128         public static final int DISPOSITION_REMOVED = 3;
1129 
1130         public final UsbPort mUsbPort;
1131         public UsbPortStatus mUsbPortStatus;
1132         public boolean mCanChangeMode;
1133         public boolean mCanChangePowerRole;
1134         public boolean mCanChangeDataRole;
1135         // default initialized to 0 which means added
1136         public int mDisposition;
1137         // Tracks elapsedRealtime() of when the port was connected
1138         public long mConnectedAtMillis;
1139         // 0 when port is connected. Else reports the last connected duration
1140         public long mLastConnectDurationMillis;
1141 
PortInfo(@onNull UsbManager usbManager, @NonNull String portId, int supportedModes, int supportedContaminantProtectionModes, boolean supportsEnableContaminantPresenceDetection, boolean supportsEnableContaminantPresenceProtection)1142         PortInfo(@NonNull UsbManager usbManager, @NonNull String portId, int supportedModes,
1143                 int supportedContaminantProtectionModes,
1144                 boolean supportsEnableContaminantPresenceDetection,
1145                 boolean supportsEnableContaminantPresenceProtection) {
1146             mUsbPort = new UsbPort(usbManager, portId, supportedModes,
1147                     supportedContaminantProtectionModes,
1148                     supportsEnableContaminantPresenceDetection,
1149                     supportsEnableContaminantPresenceProtection);
1150         }
1151 
setStatus(int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, int supportedRoleCombinations)1152         public boolean setStatus(int currentMode, boolean canChangeMode,
1153                 int currentPowerRole, boolean canChangePowerRole,
1154                 int currentDataRole, boolean canChangeDataRole,
1155                 int supportedRoleCombinations) {
1156             boolean dispositionChanged = false;
1157 
1158             mCanChangeMode = canChangeMode;
1159             mCanChangePowerRole = canChangePowerRole;
1160             mCanChangeDataRole = canChangeDataRole;
1161             if (mUsbPortStatus == null
1162                     || mUsbPortStatus.getCurrentMode() != currentMode
1163                     || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
1164                     || mUsbPortStatus.getCurrentDataRole() != currentDataRole
1165                     || mUsbPortStatus.getSupportedRoleCombinations()
1166                     != supportedRoleCombinations) {
1167                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
1168                         supportedRoleCombinations, UsbPortStatus.CONTAMINANT_PROTECTION_NONE,
1169                         UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED);
1170                 dispositionChanged = true;
1171             }
1172 
1173             if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
1174                 mConnectedAtMillis = SystemClock.elapsedRealtime();
1175                 mLastConnectDurationMillis = 0;
1176             } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
1177                 mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
1178                 mConnectedAtMillis = 0;
1179             }
1180 
1181             return dispositionChanged;
1182         }
1183 
setStatus(int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, int supportedRoleCombinations, int contaminantProtectionStatus, int contaminantDetectionStatus)1184         public boolean setStatus(int currentMode, boolean canChangeMode,
1185                 int currentPowerRole, boolean canChangePowerRole,
1186                 int currentDataRole, boolean canChangeDataRole,
1187                 int supportedRoleCombinations, int contaminantProtectionStatus,
1188                 int contaminantDetectionStatus) {
1189             boolean dispositionChanged = false;
1190 
1191             mCanChangeMode = canChangeMode;
1192             mCanChangePowerRole = canChangePowerRole;
1193             mCanChangeDataRole = canChangeDataRole;
1194             if (mUsbPortStatus == null
1195                     || mUsbPortStatus.getCurrentMode() != currentMode
1196                     || mUsbPortStatus.getCurrentPowerRole() != currentPowerRole
1197                     || mUsbPortStatus.getCurrentDataRole() != currentDataRole
1198                     || mUsbPortStatus.getSupportedRoleCombinations()
1199                     != supportedRoleCombinations
1200                     || mUsbPortStatus.getContaminantProtectionStatus()
1201                     != contaminantProtectionStatus
1202                     || mUsbPortStatus.getContaminantDetectionStatus()
1203                     != contaminantDetectionStatus) {
1204                 mUsbPortStatus = new UsbPortStatus(currentMode, currentPowerRole, currentDataRole,
1205                         supportedRoleCombinations, contaminantProtectionStatus,
1206                         contaminantDetectionStatus);
1207                 dispositionChanged = true;
1208             }
1209 
1210             if (mUsbPortStatus.isConnected() && mConnectedAtMillis == 0) {
1211                 mConnectedAtMillis = SystemClock.elapsedRealtime();
1212                 mLastConnectDurationMillis = 0;
1213             } else if (!mUsbPortStatus.isConnected() && mConnectedAtMillis != 0) {
1214                 mLastConnectDurationMillis = SystemClock.elapsedRealtime() - mConnectedAtMillis;
1215                 mConnectedAtMillis = 0;
1216             }
1217 
1218             return dispositionChanged;
1219         }
1220 
dump(@onNull DualDumpOutputStream dump, @NonNull String idName, long id)1221         void dump(@NonNull DualDumpOutputStream dump, @NonNull String idName, long id) {
1222             long token = dump.start(idName, id);
1223 
1224             writePort(dump, "port", UsbPortInfoProto.PORT, mUsbPort);
1225             writePortStatus(dump, "status", UsbPortInfoProto.STATUS, mUsbPortStatus);
1226             dump.write("can_change_mode", UsbPortInfoProto.CAN_CHANGE_MODE, mCanChangeMode);
1227             dump.write("can_change_power_role", UsbPortInfoProto.CAN_CHANGE_POWER_ROLE,
1228                     mCanChangePowerRole);
1229             dump.write("can_change_data_role", UsbPortInfoProto.CAN_CHANGE_DATA_ROLE,
1230                     mCanChangeDataRole);
1231             dump.write("connected_at_millis",
1232                     UsbPortInfoProto.CONNECTED_AT_MILLIS, mConnectedAtMillis);
1233             dump.write("last_connect_duration_millis",
1234                     UsbPortInfoProto.LAST_CONNECT_DURATION_MILLIS, mLastConnectDurationMillis);
1235 
1236             dump.end(token);
1237         }
1238 
1239         @Override
toString()1240         public String toString() {
1241             return "port=" + mUsbPort + ", status=" + mUsbPortStatus
1242                     + ", canChangeMode=" + mCanChangeMode
1243                     + ", canChangePowerRole=" + mCanChangePowerRole
1244                     + ", canChangeDataRole=" + mCanChangeDataRole
1245                     + ", connectedAtMillis=" + mConnectedAtMillis
1246                     + ", lastConnectDurationMillis=" + mLastConnectDurationMillis;
1247         }
1248     }
1249 
1250     /**
1251      * Used for storing the raw data from the kernel
1252      * Values of the member variables mocked directly incase of emulation.
1253      */
1254     private static final class RawPortInfo implements Parcelable {
1255         public final String portId;
1256         public final int supportedModes;
1257         public final int supportedContaminantProtectionModes;
1258         public int currentMode;
1259         public boolean canChangeMode;
1260         public int currentPowerRole;
1261         public boolean canChangePowerRole;
1262         public int currentDataRole;
1263         public boolean canChangeDataRole;
1264         public boolean supportsEnableContaminantPresenceProtection;
1265         public int contaminantProtectionStatus;
1266         public boolean supportsEnableContaminantPresenceDetection;
1267         public int contaminantDetectionStatus;
1268 
RawPortInfo(String portId, int supportedModes)1269         RawPortInfo(String portId, int supportedModes) {
1270             this.portId = portId;
1271             this.supportedModes = supportedModes;
1272             this.supportedContaminantProtectionModes = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
1273             this.supportsEnableContaminantPresenceProtection = false;
1274             this.contaminantProtectionStatus = UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
1275             this.supportsEnableContaminantPresenceDetection = false;
1276             this.contaminantDetectionStatus = UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
1277         }
1278 
RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes, int currentMode, boolean canChangeMode, int currentPowerRole, boolean canChangePowerRole, int currentDataRole, boolean canChangeDataRole, boolean supportsEnableContaminantPresenceProtection, int contaminantProtectionStatus, boolean supportsEnableContaminantPresenceDetection, int contaminantDetectionStatus)1279         RawPortInfo(String portId, int supportedModes, int supportedContaminantProtectionModes,
1280                 int currentMode, boolean canChangeMode,
1281                 int currentPowerRole, boolean canChangePowerRole,
1282                 int currentDataRole, boolean canChangeDataRole,
1283                 boolean supportsEnableContaminantPresenceProtection,
1284                 int contaminantProtectionStatus,
1285                 boolean supportsEnableContaminantPresenceDetection,
1286                 int contaminantDetectionStatus) {
1287             this.portId = portId;
1288             this.supportedModes = supportedModes;
1289             this.supportedContaminantProtectionModes = supportedContaminantProtectionModes;
1290             this.currentMode = currentMode;
1291             this.canChangeMode = canChangeMode;
1292             this.currentPowerRole = currentPowerRole;
1293             this.canChangePowerRole = canChangePowerRole;
1294             this.currentDataRole = currentDataRole;
1295             this.canChangeDataRole = canChangeDataRole;
1296             this.supportsEnableContaminantPresenceProtection =
1297                     supportsEnableContaminantPresenceProtection;
1298             this.contaminantProtectionStatus = contaminantProtectionStatus;
1299             this.supportsEnableContaminantPresenceDetection =
1300                     supportsEnableContaminantPresenceDetection;
1301             this.contaminantDetectionStatus = contaminantDetectionStatus;
1302         }
1303 
1304 
1305         @Override
describeContents()1306         public int describeContents() {
1307             return 0;
1308         }
1309 
1310         @Override
writeToParcel(Parcel dest, int flags)1311         public void writeToParcel(Parcel dest, int flags) {
1312             dest.writeString(portId);
1313             dest.writeInt(supportedModes);
1314             dest.writeInt(supportedContaminantProtectionModes);
1315             dest.writeInt(currentMode);
1316             dest.writeByte((byte) (canChangeMode ? 1 : 0));
1317             dest.writeInt(currentPowerRole);
1318             dest.writeByte((byte) (canChangePowerRole ? 1 : 0));
1319             dest.writeInt(currentDataRole);
1320             dest.writeByte((byte) (canChangeDataRole ? 1 : 0));
1321             dest.writeBoolean(supportsEnableContaminantPresenceProtection);
1322             dest.writeInt(contaminantProtectionStatus);
1323             dest.writeBoolean(supportsEnableContaminantPresenceDetection);
1324             dest.writeInt(contaminantDetectionStatus);
1325         }
1326 
1327         public static final Parcelable.Creator<RawPortInfo> CREATOR =
1328                 new Parcelable.Creator<RawPortInfo>() {
1329             @Override
1330             public RawPortInfo createFromParcel(Parcel in) {
1331                 String id = in.readString();
1332                 int supportedModes = in.readInt();
1333                 int supportedContaminantProtectionModes = in.readInt();
1334                 int currentMode = in.readInt();
1335                 boolean canChangeMode = in.readByte() != 0;
1336                 int currentPowerRole = in.readInt();
1337                 boolean canChangePowerRole = in.readByte() != 0;
1338                 int currentDataRole = in.readInt();
1339                 boolean canChangeDataRole = in.readByte() != 0;
1340                 boolean supportsEnableContaminantPresenceProtection = in.readBoolean();
1341                 int contaminantProtectionStatus = in.readInt();
1342                 boolean supportsEnableContaminantPresenceDetection = in.readBoolean();
1343                 int contaminantDetectionStatus = in.readInt();
1344                 return new RawPortInfo(id, supportedModes,
1345                         supportedContaminantProtectionModes, currentMode, canChangeMode,
1346                         currentPowerRole, canChangePowerRole,
1347                         currentDataRole, canChangeDataRole,
1348                         supportsEnableContaminantPresenceProtection,
1349                         contaminantProtectionStatus,
1350                         supportsEnableContaminantPresenceDetection,
1351                         contaminantDetectionStatus);
1352             }
1353 
1354             @Override
1355             public RawPortInfo[] newArray(int size) {
1356                 return new RawPortInfo[size];
1357             }
1358         };
1359     }
1360 }
1361