/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <android/looper.h>
#include <android/sensor.h>
#include <cutils/log.h>

// Hall-effect sensor type
#define SENSOR_TYPE 33171016

#define RETRY_LIMIT     120
#define RETRY_PERIOD    30          // 30 seconds
#define WARN_PERIOD     (time_t)300 // 5 minutes

/*
 * This simple daemon listens for events from the Hall-effect sensor and writes
 * the appropriate SW_LID event to a uinput node. This allows the screen to be
 * locked with a magnetic folio case.
 */
int main(void) {
    int uinputFd;
    int err;
    struct uinput_user_dev uidev;
    ASensorManager *sensorManager = nullptr;
    ASensorRef hallSensor;
    ALooper *looper;
    ASensorEventQueue *eventQueue = nullptr;
    time_t lastWarn = 0;
    int attemptCount = 0;

    ALOGI("Started");

    uinputFd = TEMP_FAILURE_RETRY(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
    if (uinputFd < 0) {
        ALOGE("Unable to open uinput node: %s", strerror(errno));
        goto out;
    }

    err = TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_SET_EVBIT, EV_SW))
        | TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_SET_EVBIT, EV_SYN))
        | TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_SET_SWBIT, SW_LID));
    if (err != 0) {
        ALOGE("Unable to enable SW_LID events: %s", strerror(errno));
        goto out;
    }

    memset(&uidev, 0, sizeof (uidev));
    snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "uinput-folio");
    uidev.id.bustype = BUS_VIRTUAL;
    uidev.id.vendor = 0;
    uidev.id.product = 0;
    uidev.id.version = 0;

    err = TEMP_FAILURE_RETRY(write(uinputFd, &uidev, sizeof (uidev)));
    if (err < 0) {
        ALOGE("Write user device to uinput node failed: %s", strerror(errno));
        goto out;
    }

    err = TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_DEV_CREATE));
    if (err < 0) {
        ALOGE("Unable to create uinput device: %s", strerror(errno));
        goto out;
    }

    ALOGI("Successfully registered uinput-folio for SW_LID events");

    // Get Hall-effect sensor events from the NDK
    sensorManager = ASensorManager_getInstanceForPackage(nullptr);
    /*
     * As long as we are unable to get the sensor handle, periodically retry
     * and emit an error message at a low frequency to prevent high CPU usage
     * and log spam. If we simply exited with an error here, we would be
     * immediately restarted and fail in the same way indefinitely.
     */
    while (true) {
        time_t now = time(NULL);
        hallSensor = ASensorManager_getDefaultSensor(sensorManager,
                                                     SENSOR_TYPE);
        if (hallSensor != nullptr) {
            break;
        }

        if (++attemptCount >= RETRY_LIMIT) {
            ALOGE("Retries exhausted; exiting");
            goto out;
        } else if (now > lastWarn + WARN_PERIOD) {
            ALOGE("Unable to get Hall-effect sensor");
            lastWarn = now;
        }

        sleep(RETRY_PERIOD);
    }

    looper = ALooper_forThread();
    if (looper == nullptr) {
        looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
    }

    eventQueue = ASensorManager_createEventQueue(sensorManager, looper, 0, NULL,
                                                 NULL);
    err = ASensorEventQueue_registerSensor(eventQueue, hallSensor,
                                           ASensor_getMinDelay(hallSensor),
                                           10000);
    if (err < 0) {
        ALOGE("Unable to register for Hall-effect sensor events");
        goto out;
    }

    ALOGI("Starting polling loop");

    // Polling loop
    while (ALooper_pollAll(-1, NULL, NULL, NULL) == 0) {
        int eventCount = 0;
        ASensorEvent sensorEvent;
        while (ASensorEventQueue_getEvents(eventQueue, &sensorEvent, 1) > 0) {
            // 1 means closed; 0 means open
            int isClosed = sensorEvent.data[0] > 0.0f ? 1 : 0;
            struct input_event event;
            event.type = EV_SW;
            event.code = SW_LID;
            event.value = isClosed;
            err = TEMP_FAILURE_RETRY(write(uinputFd, &event, sizeof (event)));
            if (err < 0) {
                ALOGE("Write EV_SW to uinput node failed: %s", strerror(errno));
                goto out;
            }

            // Force a flush with an EV_SYN
            event.type = EV_SYN;
            event.code = SYN_REPORT;
            event.value = 0;
            err = TEMP_FAILURE_RETRY(write(uinputFd, &event, sizeof (event)));
            if (err < 0) {
                ALOGE("Write EV_SYN to uinput node failed: %s",
                      strerror(errno));
                goto out;
            }

            ALOGI("Sent lid %s event", isClosed ? "closed" : "open");
            eventCount++;
        }

        if (eventCount == 0) {
            ALOGE("Poll returned with zero events: %s", strerror(errno));
            break;
        }
    }

out:
    // Clean up
    if (sensorManager != nullptr && eventQueue != nullptr) {
        ASensorManager_destroyEventQueue(sensorManager, eventQueue);
    }

    if (uinputFd >= 0) {
        close(uinputFd);
    }

    // The loop can only be exited via failure or signal
    return 1;
}