Add TouchVideoDevice

TouchVideoDevice will represent a video device that reports heatmaps.
The heatmaps are strength of the signal at each point of the
touchscreen, taken at a particular time.

The only purpose of TouchVideoDevice is to read TouchVideoFrames.

Test: this is a partial cherry-pick of a standalone binary that was used
to pull heatmaps. Integration tested by observing the action of
InputClassifier HAL.
Bug: 111480215

Change-Id: Iba1b38114b156d4debb5966b803f3df6cdb1b91c
diff --git a/services/inputflinger/EventHub.cpp b/services/inputflinger/EventHub.cpp
index a3ecebc..904580f 100644
--- a/services/inputflinger/EventHub.cpp
+++ b/services/inputflinger/EventHub.cpp
@@ -1003,14 +1003,18 @@
     } while (nWrite == -1 && errno == EINTR);
 
     if (nWrite != 1 && errno != EAGAIN) {
-        ALOGW("Could not write wake signal, errno=%d", errno);
+        ALOGW("Could not write wake signal: %s", strerror(errno));
     }
 }
 
 void EventHub::scanDevicesLocked() {
-    status_t res = scanDirLocked(DEVICE_PATH);
-    if(res < 0) {
-        ALOGE("scan dir failed for %s\n", DEVICE_PATH);
+    status_t result = scanDirLocked(DEVICE_PATH);
+    if(result < 0) {
+        ALOGE("scan dir failed for %s", DEVICE_PATH);
+    }
+    result = scanVideoDirLocked(VIDEO_DEVICE_PATH);
+    if (result != OK) {
+        ALOGE("scan video dir failed for %s", VIDEO_DEVICE_PATH);
     }
     if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
         createVirtualKeyboardLocked();
@@ -1396,6 +1400,17 @@
           toString(usingClockIoctl));
 }
 
+void EventHub::openVideoDeviceLocked(const std::string& devicePath) {
+    std::unique_ptr<TouchVideoDevice> videoDevice = TouchVideoDevice::create(devicePath);
+    if (!videoDevice) {
+        ALOGE("Could not create touch video device for %s. Ignoring", devicePath.c_str());
+        return;
+    }
+    ALOGI("Adding video device %s to list of unattached video devices",
+            videoDevice->getName().c_str());
+    mUnattachedVideoDevices.push_back(std::move(videoDevice));
+}
+
 bool EventHub::isDeviceEnabled(int32_t deviceId) {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
@@ -1575,24 +1590,31 @@
     return NAME_NOT_FOUND;
 }
 
-status_t EventHub::closeDeviceByPathLocked(const char *devicePath) {
+void EventHub::closeDeviceByPathLocked(const char *devicePath) {
     Device* device = getDeviceByPathLocked(devicePath);
     if (device) {
         closeDeviceLocked(device);
-        return 0;
+        return;
     }
     ALOGV("Remove device: %s not found, device may already have been removed.", devicePath);
-    return -1;
+}
+
+void EventHub::closeVideoDeviceByPathLocked(const std::string& devicePath) {
+    mUnattachedVideoDevices.erase(std::remove_if(mUnattachedVideoDevices.begin(),
+            mUnattachedVideoDevices.end(), [&devicePath](
+            const std::unique_ptr<TouchVideoDevice>& videoDevice) {
+            return videoDevice->getPath() == devicePath; }), mUnattachedVideoDevices.end());
 }
 
 void EventHub::closeAllDevicesLocked() {
+    mUnattachedVideoDevices.clear();
     while (mDevices.size() > 0) {
         closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1));
     }
 }
 
 void EventHub::closeDeviceLocked(Device* device) {
-    ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n",
+    ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x",
          device->path.c_str(), device->identifier.name.c_str(), device->id,
          device->fd, device->classes);
 
@@ -1670,7 +1692,12 @@
             else if (event->wd == mVideoWd) {
                 if (isV4lTouchNode(event->name)) {
                     std::string filename = StringPrintf("%s/%s", VIDEO_DEVICE_PATH, event->name);
-                    ALOGV("Received an inotify event for a video device %s", filename.c_str());
+                    if (event->mask & IN_CREATE) {
+                        openVideoDeviceLocked(filename);
+                    } else {
+                        ALOGI("Removing video device '%s' due to inotify event", filename.c_str());
+                        closeVideoDeviceByPathLocked(filename);
+                    }
                 }
             }
             else {
@@ -1708,6 +1735,30 @@
     return 0;
 }
 
+/**
+ * Look for all dirname/v4l-touch* devices, and open them.
+ */
+status_t EventHub::scanVideoDirLocked(const std::string& dirname)
+{
+    DIR* dir;
+    struct dirent* de;
+    dir = opendir(dirname.c_str());
+    if(!dir) {
+        ALOGE("Could not open video directory %s", dirname.c_str());
+        return BAD_VALUE;
+    }
+
+    while((de = readdir(dir))) {
+        const char* name = de->d_name;
+        if (isV4lTouchNode(name)) {
+            ALOGI("Found touch video device %s", name);
+            openVideoDeviceLocked(dirname + "/" + name);
+        }
+    }
+    closedir(dir);
+    return OK;
+}
+
 void EventHub::requestReopenDevices() {
     ALOGV("requestReopenDevices() called");
 
@@ -1754,6 +1805,14 @@
             dump += StringPrintf(INDENT3 "HaveKeyboardLayoutOverlay: %s\n",
                     toString(device->overlayKeyMap != nullptr));
         }
+
+        dump += INDENT "Unattached video devices:\n";
+        for (const std::unique_ptr<TouchVideoDevice>& videoDevice : mUnattachedVideoDevices) {
+            dump += INDENT2 + videoDevice->dump() + "\n";
+        }
+        if (mUnattachedVideoDevices.empty()) {
+            dump += INDENT2 "<none>\n";
+        }
     } // release lock
 }