USB: HAL: Add USB audio device bus reset recovery

During a USB bus reset, the USB host stack does not generate a removal
uevent to the USB framework.  The UsbHostManager class only relies on the
remove uevent to signal USB ALSA to stop routing audio data to the USB
headset.  Due to this limitation, when a bus reset occurs, ALSA assumes the
USB headset path is still active, and attempts to play audio to it.
However, since the audio session is never re-initialized, it results in
muted audio.

Recovery summary:
- Utilizes the authorized sysfs file to generate a device disconnect, so
that userspace can detect proper headset removal.
- Triggers off unbinding of the USB audio interfaces (not removal), which
occurs during bus reset.
- Limited to when USB headsets are connected directly to the root hub. (do
not want to disturb non-audio devices connected to an external hub)

If a disconnect is not seen after all interfaces have been unbinded (300ms
timeout) then the disconnect monitor pthread will de-authorize the root
hub.  This will generate a disconnect uevent, in which parameters are
cleaned up based on the session.  If an external hub (and devices) are
connected, then the interface counter will never reach 0, and the recovery
does not happen when a bus reset occurs (expected).

Change-Id: Ic41daf4c742e22b2aba13a270ff4d63249c0279e
diff --git a/hal/Usb.cpp b/hal/Usb.cpp
index 06b60b1..0310d0a 100644
--- a/hal/Usb.cpp
+++ b/hal/Usb.cpp
@@ -55,11 +55,19 @@
 // Set by the signal handler to destroy the thread
 volatile bool destroyThread;
 
+volatile bool armResetRecovery = false;
+std::string audioDev = "";
+volatile int monDisconnect = 0;
+pthread_t mDisMon;
+
 static void checkUsbWakeupSupport(struct Usb *usb);
 static void checkUsbInHostMode(struct Usb *usb);
 static void checkUsbDeviceAutoSuspend(const std::string& devicePath);
 static bool checkUsbInterfaceAutoSuspend(const std::string& devicePath,
         const std::string &intf);
+static bool isAudioClass(const std::string& devicePath,
+        const std::string &intf);
+static bool isRootHub(const std::string& devicePath);
 
 static int32_t readFile(const std::string &filename, std::string *contents) {
   FILE *fp;
@@ -728,14 +736,36 @@
   }
 }
 
+// USB audio device disconnect monitor
+void *disconnectMon(void *param) {
+  std::string *devicePath = (std::string *)param;
+  int timeout = 300;
+
+  while (!destroyThread && monDisconnect) {
+    if (!timeout) {
+	  ALOGI("disconnectMon timed out, deauthorizing");
+	  writeFile(*devicePath + "/../authorized", "0");
+	  break;
+	}
+	timeout--;
+	usleep(1000);
+  }
+
+  return NULL;
+}
+
 static void uevent_event(uint32_t /*epevents*/, struct data *payload) {
   char msg[UEVENT_MSG_LEN + 2];
   int n;
   std::string gadgetName = GetProperty(USB_CONTROLLER_PROP, "");
   static std::regex add_regex("add@(/devices/platform/soc/.*dwc3/xhci-hcd\\.\\d\\.auto/"
                               "usb\\d/\\d-\\d(?:/[\\d\\.-]+)*)");
+  static std::regex remove_regex("remove@((/devices/platform/soc/.*dwc3/xhci-hcd\\.\\d\\.auto/"
+                              "usb\\d)/\\d-\\d(?:/[\\d\\.-]+)*)");
   static std::regex bind_regex("bind@(/devices/platform/soc/.*dwc3/xhci-hcd\\.\\d\\.auto/"
                                "usb\\d/\\d-\\d(?:/[\\d\\.-]+)*)/([^/]*:[^/]*)");
+  static std::regex unbind_regex("unbind@(/devices/platform/soc/.*dwc3/xhci-hcd\\.\\d\\.auto/"
+                               "usb\\d/\\d-\\d(?:/[\\d\\.-]+)*)/([^/]*:[^/]*)");
   static std::regex udc_regex("(add|remove)@/devices/platform/soc/.*/" + gadgetName +
                               "/udc/" + gadgetName);
 
@@ -758,11 +788,23 @@
       std::csub_match submatch = match[1];
       checkUsbDeviceAutoSuspend("/sys" +  submatch.str());
     }
-  } else if (!payload->usb->mIgnoreWakeup && std::regex_match(msg, match, bind_regex)) {
-    if (match.size() == 3) {
-      std::csub_match devpath = match[1];
-      std::csub_match intfpath = match[2];
-      checkUsbInterfaceAutoSuspend("/sys" + devpath.str(), intfpath.str());
+  } else if (std::regex_match(msg, match, bind_regex)) {
+    std::csub_match devpath = match[1];
+    std::csub_match intfpath = match[2];
+    std::string dpath;
+
+    if (!payload->usb->mIgnoreWakeup) {
+        if (match.size() == 3) {
+          checkUsbInterfaceAutoSuspend("/sys" + devpath.str(), intfpath.str());
+        }
+    }
+
+    dpath.assign("/sys" + devpath.str());
+    // Limit the audio path recovery to devices directly connected to the root hub.
+    // Save the device path to the audio device, which will trigger the recovery.
+    if (audioDev == "" && isAudioClass(dpath, intfpath.str()) && isRootHub(dpath)) {
+       audioDev.assign(dpath);
+       armResetRecovery = true;
     }
   } else if (std::regex_match(msg, match, udc_regex)) {
     if (!strncmp(msg, "add", 3)) {
@@ -775,7 +817,6 @@
         ALOGI("Binding UDC %s to ConfigFS", gadgetName.c_str());
         writeFile("/config/usb_gadget/g1/UDC", gadgetName);
       }
-
     } else {
       // When the UDC is removed, the ConfigFS gadget will no longer be
       // bound. If ADBD is running it would keep opening/writing to its
@@ -785,6 +826,40 @@
       // Setting this property stops ADBD from proceeding with the retry.
       SetProperty(VENDOR_USB_ADB_DISABLED_PROP, "1");
     }
+  } else if (std::regex_match(msg, match, unbind_regex)) {
+    std::csub_match devpath = match[1];
+    std::csub_match intfpath = match[2];
+    std::string dpath;
+
+    dpath.assign("/sys" + devpath.str());
+    // Limit the recovery to when an audio device is connected directly to
+    // the roothub.  A path reference is needed so other non-audio class
+    // related devices don't trigger the disconnectMon. (unbind uevent occurs
+    // after sysfs files are cleaned, can't check bInterfaceClass)
+    if (armResetRecovery && audioDev == dpath) {
+        monDisconnect = 1;
+        armResetRecovery = false;
+        if (pthread_create(&mDisMon, NULL, disconnectMon, &audioDev)) {
+            ALOGE("pthread creation failed %d", errno);
+        }
+    }
+  } else if (std::regex_match(msg, match, remove_regex)) {
+    std::csub_match devpath = match[1];
+    std::csub_match parentpath = match[2];
+    std::string dpath;
+
+    dpath.assign("/sys" + devpath.str());
+    ALOGI("Disconnect received");
+    if (monDisconnect) {
+      monDisconnect = 0;
+      if (!pthread_kill(mDisMon, 0)) {
+        pthread_join(mDisMon, NULL);
+      }
+      writeFile("/sys" + parentpath.str() + "/authorized", "1");
+    }
+    if (audioDev == dpath)
+      audioDev = "";
+    armResetRecovery = false;
   }
 }
 
@@ -1023,6 +1098,27 @@
   }
 }
 
+static bool isRootHub(const std::string& devicePath) {
+  std::string devpath;
+  int path;
+
+  readFile(devicePath + "/../devpath", &devpath);
+  path = std::stoi(devpath, 0, 16);
+
+  return !path;
+}
+
+static bool isAudioClass(const std::string& devicePath,
+        const std::string &intf) {
+  std::string bInterfaceClass;
+  int interfaceClass, ret = -1;
+
+  readFile(devicePath + "/" + intf + "/bInterfaceClass", &bInterfaceClass);
+  interfaceClass = std::stoi(bInterfaceClass, 0, 16);
+
+  return (interfaceClass == USB_CLASS_AUDIO);
+}
+
 /*
  * allow specific USB device idProduct and idVendor to auto suspend
  */