1 /*
2  * Copyright 2017 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 #include <errno.h>
18 #include <fcntl.h>
19 #include <string.h>
20 #include <time.h>
21 #include <unistd.h>
22 #include <linux/input.h>
23 #include <linux/uinput.h>
24 #include <android/looper.h>
25 #include <android/sensor.h>
26 #include <cutils/log.h>
27 
28 // Hall-effect sensor type
29 #define SENSOR_TYPE 33171016
30 
31 #define RETRY_LIMIT     120
32 #define RETRY_PERIOD    30          // 30 seconds
33 #define WARN_PERIOD     (time_t)300 // 5 minutes
34 
35 /*
36  * This simple daemon listens for events from the Hall-effect sensor and writes
37  * the appropriate SW_LID event to a uinput node. This allows the screen to be
38  * locked with a magnetic folio case.
39  */
main(void)40 int main(void) {
41     int uinputFd;
42     int err;
43     struct uinput_user_dev uidev;
44     ASensorManager *sensorManager = nullptr;
45     ASensorRef hallSensor;
46     ALooper *looper;
47     ASensorEventQueue *eventQueue = nullptr;
48     int32_t hallMinDelay = 0;
49     time_t lastWarn = 0;
50     int attemptCount = 0;
51 
52     ALOGI("Started");
53 
54     uinputFd = TEMP_FAILURE_RETRY(open("/dev/uinput", O_WRONLY | O_NONBLOCK));
55     if (uinputFd < 0) {
56         ALOGE("Unable to open uinput node: %s", strerror(errno));
57         goto out;
58     }
59 
60     err = TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_SET_EVBIT, EV_SW))
61         | TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_SET_EVBIT, EV_SYN))
62         | TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_SET_SWBIT, SW_LID));
63     if (err != 0) {
64         ALOGE("Unable to enable SW_LID events: %s", strerror(errno));
65         goto out;
66     }
67 
68     memset(&uidev, 0, sizeof (uidev));
69     snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "uinput-folio");
70     uidev.id.bustype = BUS_VIRTUAL;
71     uidev.id.vendor = 0;
72     uidev.id.product = 0;
73     uidev.id.version = 0;
74 
75     err = TEMP_FAILURE_RETRY(write(uinputFd, &uidev, sizeof (uidev)));
76     if (err < 0) {
77         ALOGE("Write user device to uinput node failed: %s", strerror(errno));
78         goto out;
79     }
80 
81     err = TEMP_FAILURE_RETRY(ioctl(uinputFd, UI_DEV_CREATE));
82     if (err < 0) {
83         ALOGE("Unable to create uinput device: %s", strerror(errno));
84         goto out;
85     }
86 
87     ALOGI("Successfully registered uinput-folio for SW_LID events");
88 
89     // Get Hall-effect sensor events from the NDK
90     sensorManager = ASensorManager_getInstanceForPackage(nullptr);
91     looper = ALooper_forThread();
92     if (looper == nullptr) {
93         looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
94     }
95 
96     eventQueue = ASensorManager_createEventQueue(sensorManager, looper, 0, NULL,
97                                                  NULL);
98 
99     /*
100      * As long as we are unable to get the sensor handle, periodically retry
101      * and emit an error message at a low frequency to prevent high CPU usage
102      * and log spam. If we simply exited with an error here, we would be
103      * immediately restarted and fail in the same way indefinitely.
104      */
105     while (true) {
106         time_t now = time(NULL);
107         hallSensor = ASensorManager_getDefaultSensor(sensorManager,
108                                                      SENSOR_TYPE);
109         if (hallSensor != nullptr) {
110             hallMinDelay = ASensor_getMinDelay(hallSensor);
111             break;
112         }
113 
114         if (++attemptCount >= RETRY_LIMIT) {
115             ALOGE("Retries exhausted; exiting");
116             goto out;
117         } else if (now > lastWarn + WARN_PERIOD) {
118             ALOGE("Unable to get Hall-effect sensor");
119             lastWarn = now;
120         }
121 
122         sleep(RETRY_PERIOD);
123     }
124 
125     err = ASensorEventQueue_registerSensor(eventQueue, hallSensor,
126                                            hallMinDelay, 10000);
127     if (err < 0) {
128         ALOGE("Unable to register for Hall-effect sensor events");
129         goto out;
130     }
131 
132     ALOGI("Starting polling loop");
133 
134     // Polling loop
135     while (ALooper_pollAll(-1, NULL, NULL, NULL) == 0) {
136         int eventCount = 0;
137         ASensorEvent sensorEvent;
138         while (ASensorEventQueue_getEvents(eventQueue, &sensorEvent, 1) > 0) {
139             // 1 means closed; 0 means open
140             int isClosed = sensorEvent.data[0] > 0.0f ? 1 : 0;
141             struct input_event event;
142             event.type = EV_SW;
143             event.code = SW_LID;
144             event.value = isClosed;
145             err = TEMP_FAILURE_RETRY(write(uinputFd, &event, sizeof (event)));
146             if (err < 0) {
147                 ALOGE("Write EV_SW to uinput node failed: %s", strerror(errno));
148                 goto out;
149             }
150 
151             // Force a flush with an EV_SYN
152             event.type = EV_SYN;
153             event.code = SYN_REPORT;
154             event.value = 0;
155             err = TEMP_FAILURE_RETRY(write(uinputFd, &event, sizeof (event)));
156             if (err < 0) {
157                 ALOGE("Write EV_SYN to uinput node failed: %s",
158                       strerror(errno));
159                 goto out;
160             }
161 
162             ALOGI("Sent lid %s event", isClosed ? "closed" : "open");
163             eventCount++;
164         }
165 
166         if (eventCount == 0) {
167             ALOGE("Poll returned with zero events: %s", strerror(errno));
168             break;
169         }
170     }
171 
172 out:
173     // Clean up
174     if (sensorManager != nullptr && eventQueue != nullptr) {
175         ASensorManager_destroyEventQueue(sensorManager, eventQueue);
176     }
177 
178     if (uinputFd >= 0) {
179         close(uinputFd);
180     }
181 
182     // The loop can only be exited via failure or signal
183     return 1;
184 }
185