KeyHandler: Integrate in tri-state-key handling via UEventObserver API

This basically adds required code to support both switch and extcon
based tri-state-key. This also adds tri-state-key-calibrate script for
devices that need to be calibrated on boot (OnePlus7+)

Change-Id: I2e6c5d2861569750bead05edacf6e328a5227077
This commit is contained in:
Timi Rautamäki
2021-10-19 13:08:14 +00:00
committed by LuK1337
parent acbbb33e8f
commit 75ea79851b
14 changed files with 118 additions and 465 deletions

View File

@@ -17,3 +17,10 @@ android_app {
proguard_flags_files: ["proguard.flags"],
},
}
sh_binary {
name: "tri-state-key-calibrate",
init_rc: ["tri-state-key-calibrate.rc"],
src: "tri-state-key-calibrate.sh",
vendor: true,
}

View File

@@ -17,6 +17,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:sharedUserId="android.uid.system"
package="org.lineageos.settings.device">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
@@ -26,6 +27,15 @@
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
<receiver
android:name="org.lineageos.settings.device.BootCompletedReceiver"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<provider
android:name=".ConfigPanelSearchIndexablesProvider"
android:authorities="org.lineageos.settings.device"
@@ -49,6 +59,9 @@
</intent-filter>
</activity>
<activity android:name=".KeyHandler" />
</application>
<service
android:name=".KeyHandler"
android:permission="KeyHandlerService"
android:exported="false" />
</application>
</manifest>

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2021 The LineageOS Project
* SPDX-License-Identifier: Apache-2.0
*/
package org.lineageos.settings.device
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class BootCompletedReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Starting")
context.startService(Intent(context, KeyHandler::class.java))
}
companion object {
private const val TAG = "KeyHandler"
}
}

View File

@@ -6,30 +6,27 @@
package org.lineageos.settings.device
import android.app.NotificationManager
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.media.AudioManager
import android.media.AudioSystem
import android.os.IBinder
import android.os.UEventObserver
import android.os.VibrationEffect
import android.os.Vibrator
import android.provider.Settings
import android.view.KeyEvent
import com.android.internal.os.DeviceKeyHandler
import androidx.preference.PreferenceManager
class KeyHandler(context: Context) : DeviceKeyHandler {
private val audioManager = context.getSystemService(AudioManager::class.java)
private val notificationManager = context.getSystemService(NotificationManager::class.java)
private val vibrator = context.getSystemService(Vibrator::class.java)
private val packageContext = context.createPackageContext(
KeyHandler::class.java.getPackage()!!.name, 0
)
private val sharedPreferences
get() = packageContext.getSharedPreferences(
packageContext.packageName + "_preferences",
Context.MODE_PRIVATE or Context.MODE_MULTI_PROCESS
)
class KeyHandler : Service() {
private lateinit var audioManager: AudioManager
private lateinit var notificationManager: NotificationManager
private lateinit var vibrator: Vibrator
private lateinit var sharedPreferences: SharedPreferences
private var wasMuted = false
private val broadcastReceiver = object : BroadcastReceiver() {
@@ -42,30 +39,50 @@ class KeyHandler(context: Context) : DeviceKeyHandler {
}
}
init {
context.registerReceiver(
broadcastReceiver,
IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION)
)
}
private val alertSliderEventObserver = object : UEventObserver() {
private val lock = Any()
override fun handleKeyEvent(event: KeyEvent): KeyEvent {
if (event.action == KeyEvent.ACTION_DOWN) {
when (event.scanCode) {
POSITION_TOP -> {
handleMode(sharedPreferences.getString(ALERT_SLIDER_TOP_KEY, "0")!!.toInt())
override fun onUEvent(event: UEvent) {
synchronized(lock) {
event.get("SWITCH_STATE")?.let {
handleMode(it.toInt())
return
}
POSITION_MIDDLE -> {
handleMode(sharedPreferences.getString(ALERT_SLIDER_MIDDLE_KEY, "1")!!.toInt())
}
POSITION_BOTTOM -> {
handleMode(sharedPreferences.getString(ALERT_SLIDER_BOTTOM_KEY, "2")!!.toInt())
event.get("STATE")?.let {
val none = it.contains("USB=0")
val vibration = it.contains("HOST=0")
val silent = it.contains("null)=0")
if (none && !vibration && !silent) {
handleMode(POSITION_BOTTOM)
} else if (!none && vibration && !silent) {
handleMode(POSITION_MIDDLE)
} else if (!none && !vibration && silent) {
handleMode(POSITION_TOP)
}
return
}
}
}
return event
}
override fun onCreate() {
audioManager = getSystemService(AudioManager::class.java)
notificationManager = getSystemService(NotificationManager::class.java)
vibrator = getSystemService(Vibrator::class.java)
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
registerReceiver(
broadcastReceiver,
IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION)
)
alertSliderEventObserver.startObserving("tri-state-key")
alertSliderEventObserver.startObserving("tri_state_key")
}
override fun onBind(intent: Intent?): IBinder? = null
private fun vibrateIfNeeded(mode: Int) {
when (mode) {
AudioManager.RINGER_MODE_VIBRATE -> vibrator.vibrate(MODE_VIBRATION_EFFECT)
@@ -73,9 +90,16 @@ class KeyHandler(context: Context) : DeviceKeyHandler {
}
}
private fun handleMode(mode: Int) {
private fun handleMode(position: Int) {
val muteMedia = sharedPreferences.getBoolean(MUTE_MEDIA_WITH_SILENT, false)
val mode = when (position) {
POSITION_TOP -> sharedPreferences.getString(ALERT_SLIDER_TOP_KEY, "0")!!.toInt()
POSITION_MIDDLE -> sharedPreferences.getString(ALERT_SLIDER_MIDDLE_KEY, "1")!!.toInt()
POSITION_BOTTOM -> sharedPreferences.getString(ALERT_SLIDER_BOTTOM_KEY, "2")!!.toInt()
else -> return
}
when (mode) {
AudioManager.RINGER_MODE_SILENT -> {
notificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG)
@@ -106,10 +130,10 @@ class KeyHandler(context: Context) : DeviceKeyHandler {
companion object {
private const val TAG = "KeyHandler"
// Slider key codes
private const val POSITION_TOP = 601
private const val POSITION_MIDDLE = 602
private const val POSITION_BOTTOM = 603
// Slider key positions
private const val POSITION_TOP = 1
private const val POSITION_MIDDLE = 2
private const val POSITION_BOTTOM = 3
// Preference keys
private const val ALERT_SLIDER_TOP_KEY = "config_top_position"

View File

@@ -0,0 +1,9 @@
service vendor.tri-state-key-calibrate /vendor/bin/tri-state-key-calibrate
class main
user system
group system
oneshot
disabled
on post-fs-data
start vendor.tri-state-key-calibrate

View File

@@ -0,0 +1,6 @@
#!/vendor/bin/sh
if [[ -f /mnt/vendor/persist/engineermode/tri_state_hall_data ]]; then
CALIBRATION_DATA="$(cat /mnt/vendor/persist/engineermode/tri_state_hall_data)"
CALIBRATION_DATA="${CALIBRATION_DATA//;/,}"
echo -n $CALIBRATION_DATA > /sys/devices/platform/soc/soc:tri_state_key/hall_data_calib
fi

View File

@@ -1,11 +0,0 @@
BasedOnStyle: Google
AccessModifierOffset: -2
AllowShortFunctionsOnASingleLine: Inline
ColumnLimit: 100
CommentPragmas: NOLINT:.*
DerivePointerAlignment: false
IndentWidth: 4
PointerAlignment: Left
TabWidth: 4
UseTab: Never
PenaltyExcessCharacter: 32

View File

@@ -1,47 +0,0 @@
//
// 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_defaults {
name: "tri-state-key_defaults",
stem: "tri-state-key_daemon",
srcs: [
"main.cpp",
"uevent_listener.cpp",
],
cppflags: [
"-Wall",
"-Werror",
],
shared_libs: [
"libbase",
"liblog",
"libcutils",
"libutils",
],
}
cc_binary {
name: "tri-state-key_daemon",
defaults: ["tri-state-key_defaults"],
init_rc: ["tri-state-key_daemon.rc"],
system_ext_specific: true,
}
cc_binary {
name: "tri-state-key_daemon.vendor",
defaults: ["tri-state-key_defaults"],
init_rc: ["vendor.tri-state-key_daemon.rc"],
vendor: true,
}

View File

@@ -1,158 +0,0 @@
/*
* 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 <android-base/file.h>
#include <android-base/logging.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <unistd.h>
#include "uevent_listener.h"
#define HALL_CALIBRATION_DATA "/sys/devices/platform/soc/soc:tri_state_key/hall_data_calib"
#define HALL_PERSIST_CALIBRATION_DATA "/mnt/vendor/persist/engineermode/tri_state_hall_data"
#define KEY_MODE_NORMAL 601
#define KEY_MODE_VIBRATION 602
#define KEY_MODE_SILENCE 603
using android::base::ReadFileToString;
using android::base::WriteStringToFile;
using android::Uevent;
using android::UeventListener;
int main() {
int err;
int uinputFd;
struct uinput_user_dev uidev {};
UeventListener uevent_listener;
LOG(INFO) << "Started";
if (std::string hallData; ReadFileToString(HALL_PERSIST_CALIBRATION_DATA, &hallData)) {
std::replace(hallData.begin(), hallData.end(), ';', ',');
WriteStringToFile(hallData, HALL_CALIBRATION_DATA);
}
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("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;
}

View File

@@ -1,10 +0,0 @@
on fs
chown system system /sys/devices/platform/soc/soc:tri_state_key/hall_data_calib
chown system system /mnt/vendor/persist/engineermode/tri_state_hall_data
start tri-state-key_daemon
service tri-state-key_daemon /system_ext/bin/tri-state-key_daemon
class late_start
user system
group system uhid

View File

@@ -1,32 +0,0 @@
/*
* 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 <string>
namespace android {
struct Uevent {
std::string action;
std::string name;
std::string state;
};
} // namespace android
#endif

View File

@@ -1,114 +0,0 @@
/*
* 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 <fcntl.h>
#include <poll.h>
#include <string.h>
#include <unistd.h>
#include <memory>
#include <android-base/logging.h>
#include <cutils/uevent.h>
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

View File

@@ -1,46 +0,0 @@
/*
* 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 <functional>
#include <android-base/unique_fd.h>
#include "uevent.h"
#define UEVENT_MSG_LEN 2048
namespace android {
using ListenerCallback = std::function<void(const Uevent&)>;
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

View File

@@ -1,10 +0,0 @@
on fs
chown system system /sys/devices/platform/soc/soc:tri_state_key/hall_data_calib
chown system system /mnt/vendor/persist/engineermode/tri_state_hall_data
start vendor.tri-state-key_daemon
service vendor.tri-state-key_daemon /vendor/bin/tri-state-key_daemon
class late_start
user system
group system uhid