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