/* * 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; }