背景
遇到一個需求,需要在Settings中新增一個開關,開關需要控制一個驅動節點,開啟時向節點寫1,關閉時寫0
看起來是個很普通的問題,但坑卻不小。
跳坑
需求大部分已經寫好,只需要在開關上寫值就可以。於是很快的去加上寫值的程式碼。
/packages/apps/Settings/src/com/android/settings/gestures/GestureSettings.java
在onPreferenceTreeClick()中適當位置加入以下任意方法
第一種寫法:
public static void setNode(String node_path){
BufferedWriter bufWriter = null;
bufWriter = new BufferedWriter(new FileWriter(NODE_PATH));
bufWriter.write("1"); // 寫操作
bufWriter.close();
Toast.makeText(getApplicationContext(),"功能已啟用",Toast.LENGTH_SHORT).show();
Log.d(TAG,"功能已啟用 angle " + getString(ANGLE_PATH));
}
複製程式碼
第二種寫法:
public static void writeSysFile(String node_path){
Process p = null;
DataOutputStream os = null;
try {
p = Runtime.getRuntime().exec("sh");
os = new DataOutputStream(p.getOutputStream());
os.writeBytes("echo 1 > "+node_path + "\n");
os.writeBytes("exit\n");
os.flush();
} catch (IOException e) {
e.printStackTrace();
Log.e(MainActivity.TAG, " can't write " + sys_path+e.getMessage());
} finally {
if(p != null){
p.destroy();
}
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
複製程式碼
這兩種寫法都是對驅動節點寫值,本質上等同於
adb root
adb remount
adb shell
:/ echo 1 > /sys/bus/i2c/drivers/fts_ts/3-0038/fts_gesture_mode
:/ cat /sys/bus/i2c/drivers/fts_ts/3-0038/fts_gesture_mode
Gesture Mode: On
Reg(0xD0) = 0
複製程式碼
其中 /sys/bus/i2c/drivers/fts_ts/3-0038/fts_gesture_mode 就是 node_path,這裡隨便寫了一個做示範。
這裡看起來還行,編譯也不會出錯。
出現問題
試了兩種寫法都未生效,這裡第二種方法相當於sh,因而只管寫入,不生效也沒有log比較坑。 第一種寫法報出的log很明顯是SELinux問題。
avc: denied { write } for name="fts_gesture_mode" dev="sysfs" ino=29040 scontext=u:r:system_app:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=0
複製程式碼
關於這類報錯,有很多介紹說明。谷歌官方 ;快速解決
簡單的說就是分解下面這段
denied { write } *** context=u:r:system_app:s0 tcontext=u:object_r:sysfs:s0 tclass=file permissive=0
複製程式碼
寫成這種格式 allow context tcontext:tclass denied;
得到這樣的語句 allow system_app sysfs:file write;
ps(因為新增上述程式碼在/packages/apps/Settings/中,即Settings應用,擁有android.uid.system,屬於system_app)
於是去“system_app.te”新增allow許可權。(system_app.te在QCOM平臺放置在devices/qcom/sepolicy/msm89**)
allow system_app sysfs:file write;
或
allow system_app sysfs:file rw_file_perms;
複製程式碼
新增完開始編譯~~
libsepol.report failure: neverallow on line 489 of system/sepolicy/private/app.te (or line 22022 of policy.conf) violated by allow system_app sysfs:file { write );
libsepol.check_assertions: 1 neverallow failures occurred
Error while expanding policy
複製程式碼
然鵝編譯報錯,看報錯資訊是 1 neverallow failures occurred
基本上,出現neverallow說明此路不通,如果只是臨時驗證,可以臨時關閉,臨時關閉的話,此時:
adb root
adb remount
adb shell
:/ setenforce 0 ##設定SELinux 成為permissive模式(SELinux開啟,但對違反selinux規則的行為只記錄,不會阻止)
:/ getenforce ##獲取SELinux狀態(permissive,enforcing,disabled)
Permissive
複製程式碼
這樣再操作開關就可以正常寫值,不會報錯。這可以用來驗證上層程式碼是否OK。 但是作為系統版本不能這麼做。強行修改neverallow語法會導致CTS問題,那就是挖了個更大的坑。
解決辦法
以上方法走不通,於是找了一會。發現有更簡便的方法。
init.rc 這個開機啟動並持續執行的服務可以解決很多問題。
回到需求本身,只是在settings的開關狀態改變時,對應的改變驅動節點值。 那麼init.rc可以做到監聽一個狀態的改變,並執行命令。(在QCOM平臺上init.rc對應為init.target.rc位於devices/qcom/msm89**)
如下格式:
on <trigger> [&& <trigger>]* //設定觸發器
<command>
<command> //動作觸發之後要執行的命令
<command>
複製程式碼
於是立刻寫上:
on property:persist.gesture.dclick=1
write /sys/bus/i2c/devices/3-0038/fts_gesture_mode 1
on property:persist.gesture.dclick=0
write /sys/bus/i2c/devices/3-0038/fts_gesture_mode 0
複製程式碼
那麼“persist.gesture.dclick”則是在settings中點選時賦值。
private void setDoubleTap(int value) {
try{
Log("Write double tap on value--" + value);
SystemProperties.set(PERSIST_GESTURES_DLICK, Integer. tostring(value));
} catch (IllegalArgumentException e) {
Log.w( TAG, e.toString());
}
}
複製程式碼
於是通過 SystemProperties、init.rc 實現了上層對驅動節點的寫操作。
同樣的,上層對驅動節點沒有許可權的操作、特殊的開機services、狀態監聽等等,都可以用此種方式解決。穩得不行。
小結
剛入framework開發,不能侷限於app開發思維,需要往底層走走看看。