diff --git a/Android.bp b/Android.bp index 2c56a96..f2c082f 100644 --- a/Android.bp +++ b/Android.bp @@ -1,3 +1,4 @@ subdirs = [ - "lights" + "lights", + "tri-state-key" ] diff --git a/common.mk b/common.mk index a223f56..021359c 100644 --- a/common.mk +++ b/common.mk @@ -103,6 +103,10 @@ PRODUCT_PACKAGES += \ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/configs/qti_whitelist.xml:system/etc/sysconfig/qti_whitelist.xml +# tri-state-key +PRODUCT_PACKAGES += \ + tri-state-key_daemon + # Update engine PRODUCT_PACKAGES += \ brillo_update_payload \ diff --git a/sepolicy/private/file_contexts b/sepolicy/private/file_contexts index c2d5347..9cf0bf0 100644 --- a/sepolicy/private/file_contexts +++ b/sepolicy/private/file_contexts @@ -8,3 +8,6 @@ # Lights /system/bin/hw/android\.hardware\.light@2\.0-service\.oneplus_sdm845 u:object_r:hal_light_sdm845_exec:s0 + +# tri-state-key +/system/bin/tri-state-key_daemon u:object_r:tri-state-key_daemon_exec:s0 diff --git a/sepolicy/private/tri-state-key.te b/sepolicy/private/tri-state-key.te new file mode 100644 index 0000000..108c7ab --- /dev/null +++ b/sepolicy/private/tri-state-key.te @@ -0,0 +1,7 @@ +type tri-state-key_daemon, domain, coredomain; +type tri-state-key_daemon_exec, exec_type, file_type; + +init_daemon_domain(tri-state-key_daemon) + +allow tri-state-key_daemon uhid_device:chr_file rw_file_perms; +allow tri-state-key_daemon self:netlink_kobject_uevent_socket create_socket_perms_no_ioctl; diff --git a/tri-state-key/.clang-format b/tri-state-key/.clang-format new file mode 100644 index 0000000..ae4a451 --- /dev/null +++ b/tri-state-key/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: Google +AccessModifierOffset: -2 +AllowShortFunctionsOnASingleLine: Inline +ColumnLimit: 100 +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +IndentWidth: 4 +PointerAlignment: Left +TabWidth: 4 +UseTab: Never +PenaltyExcessCharacter: 32 diff --git a/tri-state-key/Android.bp b/tri-state-key/Android.bp new file mode 100644 index 0000000..1416837 --- /dev/null +++ b/tri-state-key/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2018 The LineageOS 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. + +cc_binary { + name: "tri-state-key_daemon", + init_rc: ["tri-state-key_daemon.rc"], + srcs: [ + "main.cpp", + "uevent_listener.cpp", + ], + cppflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libbase", + "liblog", + "libcutils", + "libutils", + ], +} diff --git a/tri-state-key/main.cpp b/tri-state-key/main.cpp new file mode 100644 index 0000000..856b1de --- /dev/null +++ b/tri-state-key/main.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2018 The LineageOS 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. + */ + +#define LOG_TAG "tri-state-key_daemon" + +#include +#include +#include +#include +#include + +#include "uevent_listener.h" + +#define KEY_MODE_NORMAL 601 +#define KEY_MODE_VIBRATION 602 +#define KEY_MODE_SILENCE 603 + +using android::Uevent; +using android::UeventListener; + +int main() { + int err; + int uinputFd; + struct uinput_user_dev uidev {}; + UeventListener uevent_listener; + + LOG(INFO) << "Started"; + + uinputFd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); + if (uinputFd < 0) { + LOG(ERROR) << "Unable to open uinput node"; + return 1; + } + + err = ioctl(uinputFd, UI_SET_EVBIT, EV_KEY) | + ioctl(uinputFd, UI_SET_KEYBIT, KEY_MODE_NORMAL) | + ioctl(uinputFd, UI_SET_KEYBIT, KEY_MODE_VIBRATION) | + ioctl(uinputFd, UI_SET_KEYBIT, KEY_MODE_SILENCE); + if (err != 0) { + LOG(ERROR) << "Unable to enable KEY events"; + goto out; + } + + sprintf(uidev.name, "uinput-tri-state-key"); + uidev.id.bustype = BUS_VIRTUAL; + + err = write(uinputFd, &uidev, sizeof(uidev)); + if (err < 0) { + LOG(ERROR) << "Write user device to uinput node failed"; + goto out; + } + + err = ioctl(uinputFd, UI_DEV_CREATE); + if (err < 0) { + LOG(ERROR) << "Unable to create uinput device"; + goto out; + } + + LOG(INFO) << "Successfully registered uinput-tri-state-key for KEY events"; + + uevent_listener.Poll([&uinputFd](const Uevent& uevent) { + int err; + struct input_event event {}; + + if (uevent.action != "change" || uevent.name != "soc:tri_state_key") { + return; + } + + bool none = uevent.state.find("USB=0") != std::string::npos; + bool vibration = uevent.state.find("USB_HOST=0") != std::string::npos; + bool silent = uevent.state.find("null)=0") != std::string::npos; + + int keyCode; + if (none && !vibration && !silent) { + keyCode = KEY_MODE_NORMAL; + } else if (!none && vibration && !silent) { + keyCode = KEY_MODE_VIBRATION; + } else if (!none && !vibration && silent) { + keyCode = KEY_MODE_SILENCE; + } else { + // Ignore intermediate states + return; + } + + // Report the key + event.type = EV_KEY; + event.code = keyCode; + event.value = 1; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_KEY to uinput node failed"; + return; + } + + // Force a flush with an EV_SYN + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_SYN to uinput node failed"; + return; + } + + // Report the key + event.type = EV_KEY; + event.code = keyCode; + event.value = 0; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_KEY to uinput node failed"; + return; + } + + // Force a flush with an EV_SYN + event.type = EV_SYN; + event.code = SYN_REPORT; + event.value = 0; + err = write(uinputFd, &event, sizeof(event)); + if (err < 0) { + LOG(ERROR) << "Write EV_SYN to uinput node failed"; + return; + } + + return; + }); + +out: + // Clean up + close(uinputFd); + + // The loop can only be exited via failure or signal + return 1; +} diff --git a/tri-state-key/tri-state-key_daemon.rc b/tri-state-key/tri-state-key_daemon.rc new file mode 100644 index 0000000..bb42f19 --- /dev/null +++ b/tri-state-key/tri-state-key_daemon.rc @@ -0,0 +1,4 @@ +service tri-state-key_daemon /system/bin/tri-state-key_daemon + class late_start + user system + group system diff --git a/tri-state-key/uevent.h b/tri-state-key/uevent.h new file mode 100644 index 0000000..ac30ee1 --- /dev/null +++ b/tri-state-key/uevent.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 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. + */ + +#ifndef _INIT_UEVENT_H +#define _INIT_UEVENT_H + +#include + +namespace android { + +struct Uevent { + std::string action; + std::string name; + std::string state; +}; + +} // namespace android + +#endif diff --git a/tri-state-key/uevent_listener.cpp b/tri-state-key/uevent_listener.cpp new file mode 100644 index 0000000..46b77c8 --- /dev/null +++ b/tri-state-key/uevent_listener.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 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 "uevent_listener.h" + +#include +#include +#include +#include + +#include + +#include +#include + +namespace android { + +static void ParseEvent(const char* msg, Uevent* uevent) { + uevent->action.clear(); + uevent->name.clear(); + uevent->state.clear(); + + while (*msg) { + if (!strncmp(msg, "ACTION=", 7)) { + msg += 7; + uevent->action = msg; + } else if (!strncmp(msg, "NAME=", 5)) { + msg += 5; + uevent->name = msg; + } else if (!strncmp(msg, "STATE=", 6)) { + msg += 6; + uevent->state = msg; + } + // advance to after the next \0 + while (*msg++); + } + + LOG(DEBUG) << "ACTION=" << uevent->action << " NAME=" << uevent->name + << " STATE=" << uevent->state; +} + +UeventListener::UeventListener() { + // is 256K enough? udev uses 16MB! + device_fd_.reset(uevent_open_socket(256 * 1024, true)); + if (device_fd_ == -1) { + LOG(FATAL) << "Could not open uevent socket"; + } + + fcntl(device_fd_, F_SETFL, O_NONBLOCK); +} + +bool UeventListener::ReadUevent(Uevent* uevent) const { + char msg[UEVENT_MSG_LEN + 2]; + int n = uevent_kernel_multicast_recv(device_fd_, msg, UEVENT_MSG_LEN); + if (n <= 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG(ERROR) << "Error reading from Uevent Fd"; + } + return false; + } + if (n >= UEVENT_MSG_LEN) { + LOG(ERROR) << "Uevent overflowed buffer, discarding"; + // Return true here even if we discard as we may have more uevents pending and we + // want to keep processing them. + return true; + } + + msg[n] = '\0'; + msg[n + 1] = '\0'; + + ParseEvent(msg, uevent); + + return true; +} + +void UeventListener::Poll(const ListenerCallback& callback) const { + pollfd ufd; + ufd.events = POLLIN; + ufd.fd = device_fd_; + + while (true) { + ufd.revents = 0; + + int nr = poll(&ufd, 1, -1); + if (nr == 0) return; + if (nr < 0) { + PLOG(ERROR) << "poll() of uevent socket failed, continuing"; + continue; + } + if (ufd.revents & POLLIN) { + // We're non-blocking, so if we receive a poll event keep processing until + // we have exhausted all uevent messages. + Uevent uevent; + while (ReadUevent(&uevent)) { + callback(uevent); + } + } + } +} + +} // namespace android diff --git a/tri-state-key/uevent_listener.h b/tri-state-key/uevent_listener.h new file mode 100644 index 0000000..e606e7f --- /dev/null +++ b/tri-state-key/uevent_listener.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 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. + */ + +#ifndef _INIT_UEVENT_LISTENER_H +#define _INIT_UEVENT_LISTENER_H + +#include + +#include + +#include "uevent.h" + +#define UEVENT_MSG_LEN 2048 + +namespace android { + +using ListenerCallback = std::function; + +class UeventListener { + public: + UeventListener(); + + void Poll(const ListenerCallback& callback) const; + + private: + bool ReadUevent(Uevent* uevent) const; + + android::base::unique_fd device_fd_; +}; + +} // namespace android + +#endif