pipa: peripheralmanager: Revert many keyboard/pen changes

* will be reimplemented later.
This commit is contained in:
CuriousNom
2025-08-12 12:00:00 +00:00
committed by gensis01
parent e6f8ec7329
commit 8d704d3082
23 changed files with 451 additions and 1814 deletions

View File

@@ -1,5 +1,5 @@
//
// Copyright (C) 2023-2025 The LineageOS Project
// Copyright (C) 2023 The LineageOS Project
//
// SPDX-License-Identifier: Apache-2.0
//
@@ -46,10 +46,4 @@ cc_binary {
"liblog",
"libsensorndkbridge",
],
arch: {
arm: {
cflags: ["-mfpu=neon", "-mfloat-abi=softfp"],
},
},
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2023-2025 The LineageOS Project
Copyright (C) 2023 The LineageOS Project
SPDX-License-Identifier: Apache-2.0
-->
@@ -43,31 +43,5 @@
android:resource="@string/stylus_summary" />
</activity>
<activity
android:name=".KeyboardSettingsActivity"
android:label="@string/keyboard_title"
android:theme="@style/Theme.SubSettingsBase">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.connect" />
<meta-data android:name="com.android.settings.summary"
android:resource="@string/keyboard_summary" />
</activity>
<activity
android:name=".LidSettingsActivity"
android:label="@string/lid_title"
android:theme="@style/Theme.SubSettingsBase">
<intent-filter>
<action android:name="com.android.settings.action.IA_SETTINGS" />
</intent-filter>
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.connect" />
<meta-data android:name="com.android.settings.summary"
android:resource="@string/lid_summary" />
</activity>
</application>
</manifest>

View File

@@ -14,16 +14,4 @@
<string name="stylus_switch_title">Force recognize stylus</string>
<string name="stylus_switch_summary">Enable this settings to allow using third party styluses</string>
<!-- Xiaomi Keyboard -->
<string name="keyboard_title">Keyboard</string>
<string name="keyboard_summary">Keyboard Settings</string>
<string name="keyboard_switch_title">Toggle angle detection</string>
<string name="keyboard_switch_summary">This toggles the angle detection of the keyboard</string>
<!-- Smart Cover -->
<string name="lid_title">Smart Cover</string>
<string name="lid_summary">Smart Cover Settings</string>
<string name="lid_switch_title">Toggle Smart Cover behaivor</string>
<string name="lid_switch_summary">This toggles the behaivor of the smart lid</string>
</resources>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/stylus_title">
<SwitchPreference
android:key="keyboard_switch_key"
android:defaultValue="false"
android:title="@string/keyboard_switch_title"
android:summary="@string/keyboard_switch_summary"/>
</PreferenceScreen>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:title="@string/stylus_title">
<SwitchPreference
android:key="lid_switch_key"
android:defaultValue="true"
android:title="@string/lid_switch_title"
android:summary="@string/lid_switch_summary"/>
</PreferenceScreen>

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
* Copyright (C) 2023 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,57 +9,20 @@ package org.lineageos.xiaomiperipheralmanager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.SystemProperties;
import android.util.Log;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* BroadcastReceiver that initializes peripheral managers on boot
*/
public class BootCompletedReceiver extends BroadcastReceiver {
private static final String TAG = "XiaomiPeripheralManager";
private static final boolean DEBUG = SystemProperties.getBoolean("persist.xiaomi.peripherals.debug", false);
private static final boolean DEBUG = false;
@Override
public void onReceive(final Context context, Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
return;
}
logInfo("Device boot completed, initializing peripheral services");
try {
if (DEBUG) Log.d(TAG, "Received boot completed intent");
KeyboardUtils.setup(context);
logInfo("Keyboard service initialized");
} catch (Exception e) {
logError("Failed to initialize keyboard service: " + e.getMessage());
}
try {
PenUtils.setup(context);
logInfo("Pen service initialized");
} catch (Exception e) {
logError("Failed to initialize pen service: " + e.getMessage());
}
}
private void logDebug(String message) {
if (DEBUG) Log.d(TAG, getTimestamp() + message);
}
private void logInfo(String message) {
Log.i(TAG, getTimestamp() + message);
}
private void logError(String message) {
Log.e(TAG, getTimestamp() + message);
}
private String getTimestamp() {
return "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + "] ";
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.lineageos.xiaomiperipheralmanager;
import android.os.Bundle;
import android.util.Log;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
/**
* Settings activity for stylus/pen configuration
* Hosts the StylusSettingsFragment for user configuration
*/
public class KeyboardSettingsActivity extends CollapsingToolbarBaseActivity {
private static final String TAG = "XiaomiKeyboardSettings";
private static final String TAG_KEYBOARD = "keyboard";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "Opening keyboard settings");
getFragmentManager().beginTransaction().replace(
com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new KeyboardSettingsFragment(), TAG_KEYBOARD).commit();
}
}

View File

@@ -1,125 +0,0 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.lineageos.xiaomiperipheralmanager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.preference.PreferenceManager;
import androidx.preference.PreferenceFragment;
import androidx.preference.SwitchPreference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.content.ComponentName;
import android.content.Intent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class KeyboardSettingsFragment extends PreferenceFragment implements
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "XiaomiKeyboardSettings";
private static final String KEYBOARD_KEY = "keyboard_switch_key";
private static final boolean DEBUG = true;
private static final String CONF_LOCATION = "/data/misc/xiaomi_keyboard.conf";
private SharedPreferences mKeyboardPreference;
private void saveAngleDetectionPreference(boolean enabled) {
try {
File file = new File(CONF_LOCATION);
FileOutputStream fos = new FileOutputStream(file);
fos.write((enabled ? "1" : "0").getBytes());
fos.close();
logInfo("Angle detection preference saved: " + enabled);
} catch (IOException e) {
logError("Failed to save angle detection preference: " + e.getMessage());
}
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
try {
addPreferencesFromResource(R.xml.keyboard_settings);
mKeyboardPreference = PreferenceManager.getDefaultSharedPreferences(getContext());
SwitchPreference switchPreference = (SwitchPreference) findPreference(KEYBOARD_KEY);
if (switchPreference != null) {
switchPreference.setChecked(mKeyboardPreference.getBoolean(KEYBOARD_KEY, false));
switchPreference.setEnabled(true);
} else {
logError("Could not find keyboard switch preference");
}
logInfo("Keyboard settings fragment created");
} catch (Exception e) {
logError("Error creating keyboard settings: " + e.getMessage());
}
}
@Override
public void onResume() {
super.onResume();
try {
mKeyboardPreference.registerOnSharedPreferenceChangeListener(this);
logDebug("Registered preference change listener");
} catch (Exception e) {
logError("Error in onResume: " + e.getMessage());
}
}
@Override
public void onPause() {
super.onPause();
try {
mKeyboardPreference.unregisterOnSharedPreferenceChangeListener(this);
logDebug("Unregistered preference change listener");
} catch (Exception e) {
logError("Error in onPause: " + e.getMessage());
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreference, String key) {
if (KEYBOARD_KEY.equals(key)) {
try {
boolean newStatus = mKeyboardPreference.getBoolean(key, false);
saveAngleDetectionPreference(newStatus);
logInfo("Keyboard status changed: " + newStatus);
} catch (Exception e) {
logError("Error handling preference change: " + e.getMessage());
}
}
}
// Enhanced logging helpers to match other classes
private void logDebug(String message) {
if (DEBUG) Log.d(TAG, getTimestamp() + message);
}
private void logInfo(String message) {
Log.i(TAG, getTimestamp() + message);
}
private void logError(String message) {
Log.e(TAG, getTimestamp() + message);
}
private String getTimestamp() {
return "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + "] ";
}
}

View File

@@ -1,338 +1,51 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
* Copyright (C) 2023 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.lineageos.xiaomiperipheralmanager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.input.InputManager;
import android.os.FileUtils;
import android.os.SystemProperties;
import android.util.Log;
import android.view.InputDevice;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Utility class for handling Xiaomi keyboard operations at the framework level.
* Works in conjunction with the native xiaomi-keyboard service.
*/
public class KeyboardUtils {
private static final String TAG = "XiaomiKeyboard";
private static boolean DEBUG = SystemProperties.getBoolean("persist.xiaomi.keyboard.debug", false);
private static final String TAG = "XiaomiPeripheralManagerKeyboardUtils";
private static final boolean DEBUG = false;
// Xiaomi keyboard identifiers
private static final int KEYBOARD_VENDOR_ID = 5593;
private static final int KEYBOARD_PRODUCT_ID = 163;
// Communication with native service
private static final String NANODEV_PATH = "/dev/nanodev0";
private static final byte MSG_TYPE_LOCK = 41;
private static final byte MSG_TYPE_UNLOCK = 42;
private static final byte MSG_HEADER_1 = 0x31;
private static final byte MSG_HEADER_2 = 0x38;
private static final int kbVendorId = 5593;
private static final int kbProductId = 163;
private static InputManager mInputManager;
private static boolean mLastEnabledState = false;
private static boolean mIsDeviceLocked = false;
private static Context mContext = null;
private static ScreenStateReceiver mScreenStateReceiver = null;
// Add watchdog monitor and recovery
private static final long WATCHDOG_TIMEOUT_MS = 10000; // 10 seconds
private static Thread mWatchdogThread = null;
private static volatile boolean mWatchdogRunning = false;
private static volatile long mLastWatchdogCheck = 0;
/**
* Initialize the keyboard utilities and set initial state
* @param context Application context
*/
public static void setup(Context context) {
logInfo("Initializing Xiaomi keyboard framework integration");
mContext = context.getApplicationContext();
try {
if (mInputManager == null) {
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
}
// Force disable keyboard at startup for safety
setKeyboardEnabled(false);
// Register broadcast receiver for screen state changes
registerScreenStateReceiver(context);
// Start watchdog thread
startWatchdogThread();
} catch (Exception e) {
logError("Error setting up keyboard utils: " + e.getMessage());
}
}
/**
* Register a BroadcastReceiver to handle screen state changes
*/
private static void registerScreenStateReceiver(Context context) {
if (mScreenStateReceiver != null) {
return; // Already registered
}
logInfo("Registering screen state receiver");
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_USER_PRESENT);
filter.addAction(Intent.ACTION_SCREEN_ON);
mScreenStateReceiver = new ScreenStateReceiver();
context.registerReceiver(mScreenStateReceiver, filter);
}
/**
* Start watchdog thread to monitor and recover from stuck conditions
*/
private static void startWatchdogThread() {
if (mWatchdogThread != null && mWatchdogThread.isAlive()) {
return; // Thread already running
}
mWatchdogRunning = true;
mLastWatchdogCheck = System.currentTimeMillis();
mWatchdogThread = new Thread(() -> {
logInfo("Keyboard watchdog thread started");
while (mWatchdogRunning) {
try {
// Update watchdog timestamp
mLastWatchdogCheck = System.currentTimeMillis();
// Safety check: ensure keyboard is disabled in lock screen
if (mIsDeviceLocked && mLastEnabledState) {
logError("Watchdog detected keyboard enabled while locked! Forcing disable");
setKeyboardEnabled(false);
}
Thread.sleep(5000); // Check every 5 seconds
} catch (InterruptedException e) {
// Thread interrupted, continue loop
} catch (Exception e) {
logError("Watchdog thread error: " + e.getMessage());
}
}
logInfo("Keyboard watchdog thread stopped");
});
mWatchdogThread.setDaemon(true);
mWatchdogThread.setName("KeyboardWatchdog");
mWatchdogThread.start();
}
/**
* Stop the watchdog thread during cleanup
*/
private static void stopWatchdogThread() {
mWatchdogRunning = false;
if (mWatchdogThread != null) {
mWatchdogThread.interrupt();
mWatchdogThread = null;
}
}
/**
* Unregister the screen state receiver when service is destroyed
*/
public static void cleanup(Context context) {
if (mScreenStateReceiver != null) {
try {
context.unregisterReceiver(mScreenStateReceiver);
mScreenStateReceiver = null;
} catch (Exception e) {
logError("Error unregistering receiver: " + e.getMessage());
}
}
// Stop watchdog thread
stopWatchdogThread();
// Force disable keyboard on cleanup
setKeyboardEnabled(false);
}
/**
* Enable or disable the Xiaomi keyboard input device
* @param enabled Whether the keyboard should be enabled
* @return true if operation was successful, false otherwise
*/
public static boolean setKeyboardEnabled(boolean enabled) {
// If the device is locked, never enable the keyboard
if (enabled && mIsDeviceLocked) {
logDebug("Not enabling keyboard because device is locked");
return false;
}
// Update watchdog timestamp to show activity
mLastWatchdogCheck = System.currentTimeMillis();
if (enabled == mLastEnabledState) {
logDebug("Keyboard already in requested state: " + enabled);
return true;
}
logInfo("Setting keyboard enabled: " + enabled);
boolean success = false;
boolean deviceFound = false;
try {
if (mInputManager == null) {
logError("InputManager not initialized");
return false;
}
public static void setKeyboardEnabled(boolean enabled) {
if (DEBUG) Log.d(TAG, "setKeyboardEnabled: " + enabled);
for (int id : mInputManager.getInputDeviceIds()) {
if (isDeviceXiaomiKeyboard(id)) {
deviceFound = true;
logDebug("Found Xiaomi Keyboard with id: " + id);
// Apply enable/disable with timeout protection
try {
if (DEBUG) Log.d(TAG, "setKeyboardEnabled: Found Xiaomi Keyboard with id: " + id);
if (enabled) {
if (DEBUG) Log.d(TAG, "setKeyboardEnabled: Enabling Xiaomi Keyboard");
mInputManager.enableInputDevice(id);
} else {
if (DEBUG) Log.d(TAG, "setKeyboardEnabled: Disabling Xiaomi Keyboard");
mInputManager.disableInputDevice(id);
}
mLastEnabledState = enabled;
success = true;
} catch (Exception e) {
logError("Failed to change keyboard state: " + e.getMessage());
}
}
}
if (!deviceFound) {
logInfo("Xiaomi keyboard not found in input devices");
}
} catch (Exception e) {
logError("Error changing keyboard state: " + e.getMessage());
}
return success;
}
/**
* Set device lock state and notify native service
* @param isLocked Whether the device is locked
*/
public static void setDeviceLockState(boolean isLocked) {
mIsDeviceLocked = isLocked;
logInfo("Device lock state changed: " + (isLocked ? "LOCKED" : "UNLOCKED"));
// If locked, force disable the keyboard with higher priority
if (isLocked) {
setKeyboardEnabled(false);
} else if (!mLastEnabledState) {
// Re-enable keyboard if unlocked and currently disabled
setKeyboardEnabled(true);
}
// Notify native service about lock state change
sendLockStateToNativeService(isLocked);
}
/**
* Send lock state message to native service
* @param isLocked Whether device is locked
*/
private static void sendLockStateToNativeService(boolean isLocked) {
try {
byte messageType = isLocked ? MSG_TYPE_LOCK : MSG_TYPE_UNLOCK;
// Protocol: [0x??][Header1][Header2][0][MessageType][1][1]
byte[] message = new byte[7];
message[0] = 0; // First byte can be anything
message[1] = MSG_HEADER_1;
message[2] = MSG_HEADER_2;
message[3] = 0;
message[4] = messageType;
message[5] = 1;
message[6] = 1;
// Write to nanodev device
File nanodev = new File(NANODEV_PATH);
if (!nanodev.exists() || !nanodev.canWrite()) {
logError("Cannot write to nanodev: " + NANODEV_PATH);
return;
}
FileOutputStream fos = new FileOutputStream(NANODEV_PATH);
fos.write(message);
fos.close();
logDebug("Sent lock state " + (isLocked ? "LOCK" : "UNLOCK") + " to native service");
} catch (IOException e) {
logError("Failed to send lock state to native service: " + e.getMessage());
}
}
/**
* Check if an input device is the Xiaomi keyboard
* @param id Device ID to check
* @return true if the device is the Xiaomi keyboard
*/
private static boolean isDeviceXiaomiKeyboard(int id) {
try {
InputDevice inputDevice = mInputManager.getInputDevice(id);
if (inputDevice == null) return false;
return inputDevice.getVendorId() == KEYBOARD_VENDOR_ID &&
inputDevice.getProductId() == KEYBOARD_PRODUCT_ID;
} catch (Exception e) {
logError("Error checking device: " + e.getMessage());
return false;
}
}
/**
* BroadcastReceiver to detect screen state changes
*/
private static class ScreenStateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
setDeviceLockState(true);
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
setDeviceLockState(false);
}
}
}
// Enhanced logging helpers to match C++ style
private static void logDebug(String message) {
if (DEBUG) Log.d(TAG, getTimestamp() + message);
}
private static void logInfo(String message) {
Log.i(TAG, getTimestamp() + message);
}
private static void logError(String message) {
Log.e(TAG, getTimestamp() + message);
}
private static String getTimestamp() {
return "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + "] ";
return inputDevice.getVendorId() == kbVendorId && inputDevice.getProductId() == kbProductId;
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.lineageos.xiaomiperipheralmanager;
import android.os.Bundle;
import android.util.Log;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
/**
* Settings activity for stylus/pen configuration
* Hosts the StylusSettingsFragment for user configuration
*/
public class LidSettingsActivity extends CollapsingToolbarBaseActivity {
private static final String TAG = "XiaomiLidSettings";
private static final String TAG_LID = "lid";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "Opening lid settings");
getFragmentManager().beginTransaction().replace(
com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new LidSettingsFragment(), TAG_LID).commit();
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.lineageos.xiaomiperipheralmanager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.SystemProperties;
import android.util.Log;
import android.preference.PreferenceManager;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceChangeListener;
import androidx.preference.PreferenceFragment;
import androidx.preference.SwitchPreference;
import com.android.settingslib.widget.MainSwitchPreference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.provider.Settings;
/**
* Settings fragment for lid configuration
* Allows users to manually enable/disable the smart cover
*/
public class LidSettingsFragment extends PreferenceFragment implements
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "XiaomiLidSettings";
private static final String LID_KEY = "lid_switch_key";
private SharedPreferences mLidPreference;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
try {
addPreferencesFromResource(R.xml.lid_settings);
mLidPreference = PreferenceManager.getDefaultSharedPreferences(getContext());
SwitchPreference switchPreference = (SwitchPreference) findPreference(LID_KEY);
if (switchPreference != null) {
switchPreference.setChecked(mLidPreference.getBoolean(LID_KEY, false));
switchPreference.setEnabled(true);
} else {
logError("Could not find lid switch preference");
}
logInfo("Lid settings fragment created");
} catch (Exception e) {
logError("Error creating lid settings: " + e.getMessage());
}
}
@Override
public void onResume() {
super.onResume();
try {
mLidPreference.registerOnSharedPreferenceChangeListener(this);
} catch (Exception e) {
logError("Error in onResume: " + e.getMessage());
}
}
@Override
public void onPause() {
super.onPause();
try {
mLidPreference.unregisterOnSharedPreferenceChangeListener(this);
} catch (Exception e) {
logError("Error in onPause: " + e.getMessage());
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreference, String key) {
if (LID_KEY.equals(key)) {
try {
boolean newStatus = mLidPreference.getBoolean(key, false);
logInfo("Lid preference changed to: " + newStatus);
Settings.Global.putInt(getActivity().getContentResolver(),
"lid_behavior", newStatus ? 1 : 0);
} catch (Exception e) {
logError("Error handling preference change: " + e.getMessage());
}
}
}
private void logInfo(String message) {
Log.i(TAG, getTimestamp() + message);
}
private void logError(String message) {
Log.e(TAG, getTimestamp() + message);
}
private String getTimestamp() {
return "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + "] ";
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
* Copyright (C) 2023 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -16,151 +16,73 @@ import android.view.InputDevice;
import android.preference.PreferenceManager;
import android.content.SharedPreferences;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Utility class for handling Xiaomi pen operations and mode switching
*/
public class PenUtils {
private static final String TAG = "XiaomiPen";
private static boolean DEBUG = SystemProperties.getBoolean("persist.xiaomi.pen.debug", false);
private static final String TAG = "XiaomiPeripheralManagerPenUtils";
private static final boolean DEBUG = false;
// Xiaomi pen identifiers
private static final int PEN_VENDOR_ID = 6421;
private static final int PEN_PRODUCT_ID = 19841;
private static final int penVendorId = 6421;
private static final int penProductId = 19841;
private static InputManager mInputManager;
private static SharedPreferences mPreferences;
private static final String STYLUS_KEY = "stylus_switch_key";
/**
* Initialize the pen utilities and register for input device events
* @param context Application context
*/
private static SharedPreferences preferences;
private static RefreshUtils mRefreshUtils;
public static void setup(Context context) {
logInfo("Initializing Xiaomi pen framework integration");
try {
mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
mInputManager.registerInputDeviceListener(mInputDeviceListener, null);
mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences = PreferenceManager.getDefaultSharedPreferences(context);
mRefreshUtils = new RefreshUtils(context);
refreshPenMode();
} catch (Exception e) {
logError("Error setting up pen utils: " + e.getMessage());
}
}
/**
* Enable pen mode by setting system property
*/
public static void enablePenMode() {
logInfo("Enabling pen mode");
try {
Log.d(TAG, "enablePenMode: Enable Pen Mode");
SystemProperties.set("persist.vendor.parts.pen", "18");
} catch (Exception e) {
logError("Failed to enable pen mode: " + e.getMessage());
}
Log.d(TAG, "enablePenMode: Setting Refresh Rates for Pen");
mRefreshUtils.setPenRefreshRate();
}
/**
* Disable pen mode by setting system property
*/
public static void disablePenMode() {
logInfo("Disabling pen mode");
try {
Log.d(TAG, "disablePenMode: Disable Pen Mode");
SystemProperties.set("persist.vendor.parts.pen", "2");
} catch (Exception e) {
logError("Failed to disable pen mode: " + e.getMessage());
}
Log.d(TAG, "disablePenMode: Resetting Refresh Rate Values");
mRefreshUtils.setDefaultRefreshRate();
}
/**
* Check for pen presence and update mode accordingly
*/
private static void refreshPenMode() {
try {
boolean penFound = false;
for (int id : mInputManager.getInputDeviceIds()) {
if (isDeviceXiaomiPen(id)) {
logDebug("Found Xiaomi Pen with id: " + id);
penFound = true;
break;
}
}
// Also check preference override
boolean preferenceEnabled = mPreferences.getBoolean(STYLUS_KEY, false);
if (penFound || preferenceEnabled) {
logInfo("Pen detected or enabled in preferences");
if (isDeviceXiaomiPen(id) || preferences.getBoolean(STYLUS_KEY, false)) {
if (DEBUG) Log.d(TAG, "refreshPenMode: Found Xiaomi Pen");
enablePenMode();
} else {
logInfo("No pen detected and not enabled in preferences");
return;
}
}
if (DEBUG) Log.d(TAG, "refreshPenMode: No Xiaomi Pen found");
disablePenMode();
}
} catch (Exception e) {
logError("Error refreshing pen mode: " + e.getMessage());
}
}
/**
* Check if an input device is the Xiaomi pen
* @param id Device ID to check
* @return true if the device is the Xiaomi pen
*/
private static boolean isDeviceXiaomiPen(int id) {
try {
InputDevice inputDevice = mInputManager.getInputDevice(id);
if (inputDevice == null) return false;
return inputDevice.getVendorId() == PEN_VENDOR_ID &&
inputDevice.getProductId() == PEN_PRODUCT_ID;
} catch (Exception e) {
logError("Error checking pen device: " + e.getMessage());
return false;
}
return inputDevice.getVendorId() == penVendorId &&
inputDevice.getProductId() == penProductId;
}
/**
* Input device listener for pen connection/disconnection events
*/
private static InputDeviceListener mInputDeviceListener = new InputDeviceListener() {
@Override
public void onInputDeviceAdded(int id) {
logDebug("Input device added: " + id);
refreshPenMode();
}
@Override
public void onInputDeviceRemoved(int id) {
logDebug("Input device removed: " + id);
refreshPenMode();
}
@Override
public void onInputDeviceChanged(int id) {
logDebug("Input device changed: " + id);
refreshPenMode();
}
};
// Enhanced logging helpers to match other classes
private static void logDebug(String message) {
if (DEBUG) Log.d(TAG, getTimestamp() + message);
}
private static void logInfo(String message) {
Log.i(TAG, getTimestamp() + message);
}
private static void logError(String message) {
Log.e(TAG, getTimestamp() + message);
}
private static String getTimestamp() {
return "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + "] ";
}
}

View File

@@ -0,0 +1,55 @@
package org.lineageos.xiaomiperipheralmanager;
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings;
public final class RefreshUtils {
private static final String KEY_PEAK_REFRESH_RATE = "peak_refresh_rate";
private static final String KEY_MIN_REFRESH_RATE = "min_refresh_rate";
private static final String KEY_PEN_MODE = "pen_mode";
private static final String PREF_FILE_NAME = "pen_refresh_prefs";
private Context mContext;
private SharedPreferences mSharedPrefs;
protected RefreshUtils(Context context) {
mContext = context;
mSharedPrefs = context.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
}
protected void setPenRefreshRate() {
boolean penMode = mSharedPrefs.getBoolean(KEY_PEN_MODE, false);
if (!penMode) {
float maxRate = Settings.System.getFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, 144f);
float minRate = Settings.System.getFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, 144f);
// Update default values in SharedPreferences
mSharedPrefs.edit()
.putFloat(KEY_MIN_REFRESH_RATE, minRate)
.putFloat(KEY_PEAK_REFRESH_RATE, maxRate)
.putBoolean(KEY_PEN_MODE, true)
.apply();
// Ensure valid values for maxRate and minRate
maxRate = (maxRate != 60) ? 120 : maxRate;
minRate = (minRate <= 60) ? 60 : 120;
// Set the values in the Settings.System
Settings.System.putFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, minRate);
Settings.System.putFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, maxRate);
}
}
protected void setDefaultRefreshRate() {
float defaultMinRate = mSharedPrefs.getFloat(KEY_MIN_REFRESH_RATE, 144f);
float defaultMaxRate = mSharedPrefs.getFloat(KEY_PEAK_REFRESH_RATE, 144f);
mSharedPrefs.edit().putBoolean(KEY_PEN_MODE, false).apply();
// Set the values in the Settings.System directly
Settings.System.putFloat(mContext.getContentResolver(), KEY_MIN_REFRESH_RATE, defaultMinRate);
Settings.System.putFloat(mContext.getContentResolver(), KEY_PEAK_REFRESH_RATE, defaultMaxRate);
}
}

View File

@@ -1,31 +1,33 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
* Copyright (C) 2023 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
* 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.
*/
package org.lineageos.xiaomiperipheralmanager;
import android.os.Bundle;
import android.util.Log;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
/**
* Settings activity for stylus/pen configuration
* Hosts the StylusSettingsFragment for user configuration
*/
public class StylusSettingsActivity extends CollapsingToolbarBaseActivity {
private static final String TAG = "XiaomiPenSettings";
private static final String TAG_STYLUS = "stylus";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, "Opening stylus settings");
getFragmentManager().beginTransaction().replace(
com.android.settingslib.collapsingtoolbar.R.id.content_frame,
new StylusSettingsFragment(), TAG_STYLUS).commit();

View File

@@ -1,7 +1,17 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
* Copyright (C) 2023 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
* 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.
*/
package org.lineageos.xiaomiperipheralmanager;
@@ -9,7 +19,7 @@ package org.lineageos.xiaomiperipheralmanager;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.SystemProperties;
import android.widget.Switch;
import android.util.Log;
import android.preference.PreferenceManager;
@@ -20,108 +30,53 @@ import androidx.preference.PreferenceFragment;
import androidx.preference.SwitchPreference;
import com.android.settingslib.widget.MainSwitchPreference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.lineageos.xiaomiperipheralmanager.PenUtils;
import org.lineageos.xiaomiperipheralmanager.R;
/**
* Settings fragment for stylus/pen configuration
* Allows users to manually enable/disable the pen mode
*/
public class StylusSettingsFragment extends PreferenceFragment implements
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "XiaomiPenSettings";
private static boolean DEBUG = SystemProperties.getBoolean("persist.xiaomi.pen.debug", false);
private static final String TAG = "XiaomiPeripheralManagerPenUtils";
private static final String STYLUS_KEY = "stylus_switch_key";
private SharedPreferences mStylusPreference;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
try {
addPreferencesFromResource(R.xml.stylus_settings);
mStylusPreference = PreferenceManager.getDefaultSharedPreferences(getContext());
SwitchPreference switchPreference = (SwitchPreference) findPreference(STYLUS_KEY);
if (switchPreference != null) {
switchPreference.setChecked(mStylusPreference.getBoolean(STYLUS_KEY, false));
switchPreference.setEnabled(true);
} else {
logError("Could not find stylus switch preference");
}
logInfo("Stylus settings fragment created");
} catch (Exception e) {
logError("Error creating stylus settings: " + e.getMessage());
}
}
@Override
public void onResume() {
super.onResume();
try {
mStylusPreference.registerOnSharedPreferenceChangeListener(this);
logDebug("Registered preference change listener");
} catch (Exception e) {
logError("Error in onResume: " + e.getMessage());
}
}
@Override
public void onPause() {
super.onPause();
try {
mStylusPreference.unregisterOnSharedPreferenceChangeListener(this);
logDebug("Unregistered preference change listener");
} catch (Exception e) {
logError("Error in onPause: " + e.getMessage());
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreference, String key) {
if (STYLUS_KEY.equals(key)) {
try {
boolean newStatus = mStylusPreference.getBoolean(key, false);
logInfo("Stylus preference changed to: " + newStatus);
forceStylus(newStatus);
} catch (Exception e) {
logError("Error handling preference change: " + e.getMessage());
}
forceStylus(mStylusPreference.getBoolean(key, false));
}
}
private void forceStylus(boolean status) {
try {
mStylusPreference.edit().putBoolean(STYLUS_KEY, status).apply();
logInfo("Setting stylus mode: " + (status ? "enabled" : "disabled"));
if (status) {
if (status)
PenUtils.enablePenMode();
} else {
else
PenUtils.disablePenMode();
}
} catch (Exception e) {
logError("Error setting stylus mode: " + e.getMessage());
}
}
// Enhanced logging helpers to match other classes
private void logDebug(String message) {
if (DEBUG) Log.d(TAG, getTimestamp() + message);
}
private void logInfo(String message) {
Log.i(TAG, getTimestamp() + message);
}
private void logError(String message) {
Log.e(TAG, getTimestamp() + message);
}
private String getTimestamp() {
return "[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(new Date()) + "] ";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2023-2025 The LineageOS Project
* Copyright (C) 2023 The LineageOS Project
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -8,12 +8,7 @@
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#define VERSION_STRING "1.0.0"
// Device control definitions
#define SET_CUR_VALUE 0
#define TOUCH_PEN_MODE 20
#define TOUCH_MAGIC 't'
@@ -21,41 +16,12 @@
#define TOUCH_DEV_PATH "/dev/xiaomi-touch"
int main(int argc, char **argv) {
// Validate arguments
if(argc != 2) {
fprintf(stderr, "Xiaomi pen utility v%s\n", VERSION_STRING);
fprintf(stderr, "Usage: %s <value>\n", argv[0]);
return -1;
}
// Open the touch device
int fd = open(TOUCH_DEV_PATH, O_RDWR);
if (fd < 0) {
fprintf(stderr, "Error opening device %s: %s\n",
TOUCH_DEV_PATH, strerror(errno));
return -1;
}
// Parse the input value
int value = atoi(argv[1]);
if (value < 0 || value > 20) {
fprintf(stderr, "Warning: Value %d outside normal range (0-20)\n", value);
}
fprintf(stdout, "Setting pen mode to: %d\n", value);
// Prepare and send the command
int arg[2] = {TOUCH_PEN_MODE, value};
int result = ioctl(fd, TOUCH_IOC_SETMODE, &arg);
// Check for errors
if (result < 0) {
fprintf(stderr, "Error setting pen mode: %s\n", strerror(errno));
int arg[2] = {TOUCH_PEN_MODE, atoi(argv[1])};
ioctl(fd, TOUCH_IOC_SETMODE, &arg);
close(fd);
return -1;
}
// Clean up
close(fd);
fprintf(stdout, "Pen mode set successfully\n");
return 0;
}

View File

@@ -30,7 +30,3 @@ service xiaomi-keyboard /vendor/bin/xiaomi-keyboard
on property:persist.vendor.parts.pen=*
start xiaomi-pen
on post-data-fs
exec - system system -- /system/bin/touch /data/misc/xiaomi_keyboard.conf
exec - system system -- /system/bin/restorecon /data/misc/xiaomi_keyboard.conf

View File

@@ -1 +0,0 @@
type xiaomi_keyboard_conf_file, file_type;

View File

@@ -9,5 +9,3 @@
# Xiaomi Keyboard
/dev/nanodev0 u:object_r:xiaomi_keyboard_device:s0
/data/vendor/xiaomi_keyboard.conf u:object_r:xiaomi_keyboard_conf_file:s0

View File

@@ -1 +0,0 @@
allow servicemanager xiaomi_keyboard:binder call;

View File

@@ -1,2 +1 @@
set_prop(system_app, vendor_pen_prop)
allow system_app xiaomi_keyboard_conf_file:file { read write open getattr };

View File

@@ -9,20 +9,8 @@ get_prop(xiaomi_keyboard, hwservicemanager_prop)
binder_call(xiaomi_keyboard, hwservicemanager)
binder_call(xiaomi_keyboard, system_server)
# Enhanced permissions for keyboard device
allow xiaomi_keyboard xiaomi_keyboard_device:chr_file { getattr open read write ioctl };
allow xiaomi_keyboard xiaomi_keyboard_device:chr_file { open read write };
allow xiaomi_keyboard fwk_sensor_hwservice:hwservice_manager find;
allow xiaomi_keyboard fwk_sensor_service:service_manager find;
# Enhanced input device permissions
allow xiaomi_keyboard input_device:dir { read search open };
allow xiaomi_keyboard input_device:file { read open getattr };
allow xiaomi_keyboard input_device:dir search;
allow xiaomi_keyboard servicemanager:binder { call transfer };
# Additional diagnostic permissions
allow xiaomi_keyboard sysfs:dir { read open };
allow xiaomi_keyboard sysfs_devices_system_cpu:file { read open };
allow xiaomi_keyboard xiaomi_keyboard_conf_file:file { read open };
typeattribute xiaomi_keyboard_conf_file data_file_type;