diff --git a/CloverParts/Android.bp b/CloverParts/Android.bp
index 2f82f59c..ca463c89 100644
--- a/CloverParts/Android.bp
+++ b/CloverParts/Android.bp
@@ -10,12 +10,42 @@ android_app {
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
certificate: "platform",
+ init_rc: ["cloverparts.rc"],
platform_apis: true,
system_ext_specific: true,
privileged: true,
+ static_libs: [
+ "org.pixelexperience.settings.resources",
+ "androidx.cardview_cardview",
+ "androidx.preference_preference",
+ "androidx.appcompat_appcompat",
+ "androidx.core_core",
+ "SettingsLib"
+ ],
+
optimize: {
proguard_flags_files: ["proguard.flags"],
},
+ required: [
+ "privapp-permissions_io.alcatraz.cloverparts",
+ "config-io.alcatraz.cloverparts",
+ ],
}
+
+prebuilt_etc {
+ name: "privapp-permissions_io.alcatraz.cloverparts",
+ sub_dir: "permissions",
+ src: "privapp-permissions_io.alcatraz.cloverparts.xml",
+ system_ext_specific: true,
+ filename_from_src: true,
+}
+
+prebuilt_etc {
+ name: "config-io.alcatraz.cloverparts",
+ sub_dir: "sysconfig",
+ src: "config-io.alcatraz.cloverparts.xml",
+ system_ext_specific: true,
+ filename_from_src: true,
+}
\ No newline at end of file
diff --git a/CloverParts/AndroidManifest.xml b/CloverParts/AndroidManifest.xml
index 3000941a..e0a0ef8e 100644
--- a/CloverParts/AndroidManifest.xml
+++ b/CloverParts/AndroidManifest.xml
@@ -19,14 +19,32 @@
package="io.alcatraz.cloverparts"
android:sharedUserId="android.uid.system">
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -36,5 +54,15 @@
+
+
+
+
+
+
+
+
diff --git a/CloverParts/cloverparts.rc b/CloverParts/cloverparts.rc
new file mode 100644
index 00000000..b670cc30
--- /dev/null
+++ b/CloverParts/cloverparts.rc
@@ -0,0 +1,24 @@
+#
+# Copyright (c) 2023, Alcatraz323
+#
+# 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.
+#
+
+on boot
+ chown system system /sys/class/power_supply/battery/step_charging_enabled
+ chown system system /sys/class/power_supply/battery/sw_jeita_enabled
+ chown system system /sys/class/power_supply/battery/input_suspend
+
+ chmod 0660 /sys/class/power_supply/battery/step_charging_enabled
+ chmod 0660 /sys/class/power_supply/battery/sw_jeita_enabled
+ chmod 0660 /sys/class/power_supply/battery/input_suspend
\ No newline at end of file
diff --git a/CloverParts/config-io.alcatraz.cloverparts.xml b/CloverParts/config-io.alcatraz.cloverparts.xml
new file mode 100644
index 00000000..273b3a5f
--- /dev/null
+++ b/CloverParts/config-io.alcatraz.cloverparts.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CloverParts/privapp-permissions_io.alcatraz.cloverparts.xml b/CloverParts/privapp-permissions_io.alcatraz.cloverparts.xml
new file mode 100644
index 00000000..d57eb5cb
--- /dev/null
+++ b/CloverParts/privapp-permissions_io.alcatraz.cloverparts.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CloverParts/res/drawable/ic_baseline_battery_charging_full_24.xml b/CloverParts/res/drawable/ic_baseline_battery_charging_full_24.xml
new file mode 100644
index 00000000..52f716e3
--- /dev/null
+++ b/CloverParts/res/drawable/ic_baseline_battery_charging_full_24.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/CloverParts/res/drawable/ic_baseline_miscellaneous_services_24.xml b/CloverParts/res/drawable/ic_baseline_miscellaneous_services_24.xml
new file mode 100755
index 00000000..6c8efe31
--- /dev/null
+++ b/CloverParts/res/drawable/ic_baseline_miscellaneous_services_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/CloverParts/res/drawable/ic_baseline_whatshot_24.xml b/CloverParts/res/drawable/ic_baseline_whatshot_24.xml
new file mode 100755
index 00000000..05d22635
--- /dev/null
+++ b/CloverParts/res/drawable/ic_baseline_whatshot_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/CloverParts/res/drawable/information_outline.xml b/CloverParts/res/drawable/information_outline.xml
new file mode 100644
index 00000000..35f2aa4d
--- /dev/null
+++ b/CloverParts/res/drawable/information_outline.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/CloverParts/res/values-zh-rCN/strings.xml b/CloverParts/res/values-zh-rCN/strings.xml
index 03f7a379..05311cb1 100644
--- a/CloverParts/res/values-zh-rCN/strings.xml
+++ b/CloverParts/res/values-zh-rCN/strings.xml
@@ -17,4 +17,19 @@
平板扩展
音量面板
+
+ 电源
+ 电池
+ 电池管理系统
+
+ 阶梯充电
+ 开启阶梯充电
+ 自动根据电池电压以及温度调整充电速度以保护电池
+ 充电限制器
+ 注意
+ 如果你需要打开任何充电限制器选项,建议关闭“显示 - 连接或断开电源时唤醒”,不然你可能会在充电控制期间触发一些意外唤醒
+ 持续充电模式
+ 将电量限制在40%-60%以保护电池
+ 限制最高电量为80%左右
+ 电量在80%左右时停止充电以保护电池(持续模式优先)
diff --git a/CloverParts/res/values/strings.xml b/CloverParts/res/values/strings.xml
index 595365bb..d25177a5 100644
--- a/CloverParts/res/values/strings.xml
+++ b/CloverParts/res/values/strings.xml
@@ -17,4 +17,19 @@
Clover Parts
Volume Panel
+
+ Power Supply
+ Battery
+ Battery management system
+
+ Step charging
+ Enable step charging
+ Automatically tune charge speed depends on battery data(voltage/temperature) to protect battery
+ Charge limiter
+ Notice
+ If you turn on any charge limiter option, you would better turn off the “Display - Wake on plug” or you may get some unexpected wakeups during the charge control
+ Always connected mode
+ Limit the battery percent between 40% and 60% to protect battery
+ Limit to around 80%
+ Suspend charging when the percent is around 80% to protect battery(Always connected mode will make this never happen)
diff --git a/CloverParts/res/xml/bms_settings.xml b/CloverParts/res/xml/bms_settings.xml
new file mode 100644
index 00000000..e423c973
--- /dev/null
+++ b/CloverParts/res/xml/bms_settings.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CloverParts/src/io/alcatraz/cloverparts/BMSActivity.java b/CloverParts/src/io/alcatraz/cloverparts/BMSActivity.java
new file mode 100755
index 00000000..ab092a2d
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/BMSActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2023, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.settingslib.widget.R;
+
+public class BMSActivity extends CollapsingToolbarBaseActivity {
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ Fragment fragment = getFragmentManager().findFragmentById(R.id.content_frame);
+ BMSFragment bmsFragment;
+ if (fragment == null) {
+ bmsFragment = new BMSFragment();
+ getFragmentManager().beginTransaction().add(R.id.content_frame, bmsFragment).commit();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CloverParts/src/io/alcatraz/cloverparts/BMSFragment.java b/CloverParts/src/io/alcatraz/cloverparts/BMSFragment.java
new file mode 100755
index 00000000..98e7dce5
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/BMSFragment.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2023, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.SwitchPreference;
+
+import static io.alcatraz.cloverparts.Constants.BMS_STEP_CHG_SWITCH;
+import static io.alcatraz.cloverparts.Constants.BMS_ALWAYS_CONNECTED_MODE;
+import static io.alcatraz.cloverparts.Constants.BMS_LIMIT_TO_EIGHTY;
+
+public class BMSFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener {
+ private SwitchPreference mStepChargingSwitch;
+
+ @Override
+ public void onCreatePreferences(Bundle bundle, String key) {
+ addPreferencesFromResource(R.xml.bms_settings);
+ findPreferences();
+ bindListeners();
+ updateSwitches();
+ }
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ switch (preference.getKey()) {
+ case BMS_STEP_CHG_SWITCH:
+ boolean enabled = (boolean) o;
+ ShellUtils.execCommand("echo " + (enabled ? "1" : "0") + " > /sys/class/power_supply/battery/step_charging_enabled", false);
+ ShellUtils.execCommand("echo " + (enabled ? "1" : "0") + " > /sys/class/power_supply/battery/sw_jeita_enabled", false);
+ break;
+ }
+ return true;
+ }
+
+ private void findPreferences() {
+ mStepChargingSwitch = findPreference(BMS_STEP_CHG_SWITCH);
+ }
+
+ private void bindListeners() {
+ mStepChargingSwitch.setOnPreferenceChangeListener(this);
+ }
+
+ private void updateSwitches() {
+ ShellUtils.CommandResult result = ShellUtils.execCommand("cat /sys/class/power_supply/battery/step_charging_enabled", false);
+ mStepChargingSwitch.setChecked(result.responseMsg.contains("1"));
+ }
+}
diff --git a/CloverParts/src/io/alcatraz/cloverparts/BMSReceiver.java b/CloverParts/src/io/alcatraz/cloverparts/BMSReceiver.java
new file mode 100644
index 00000000..7580aa65
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/BMSReceiver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+import android.os.BatteryManager;
+import android.os.Bundle;
+
+import static io.alcatraz.cloverparts.Constants.BMS_ALWAYS_CONNECTED_MODE;
+import static io.alcatraz.cloverparts.Constants.BMS_LIMIT_TO_EIGHTY;
+
+public class BMSReceiver extends BroadcastReceiver {
+ boolean alwaysConnected, limitToEighty;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (context == null) {
+ return;
+ }
+
+ SharedPreferenceUtil sharedPreferenceUtil = SharedPreferenceUtil.getInstance();
+ alwaysConnected = (boolean) sharedPreferenceUtil.get(context, BMS_ALWAYS_CONNECTED_MODE, false);
+ limitToEighty = (boolean) sharedPreferenceUtil.get(context, BMS_LIMIT_TO_EIGHTY, false);
+
+ processBatteryChange(intent);
+ }
+
+ private void resetSuspendState() {
+ ShellUtils.CommandResult result = ShellUtils.execCommand("cat /sys/class/power_supply/battery/input_suspend", false);
+ if(result.responseMsg.contains("1"))
+ ShellUtils.execCommand("echo 0 > /sys/class/power_supply/battery/input_suspend", false);
+ }
+
+ private void suspendCharger() {
+ ShellUtils.CommandResult result = ShellUtils.execCommand("cat /sys/class/power_supply/battery/input_suspend", false);
+ if(result.responseMsg.contains("0"))
+ ShellUtils.execCommand("echo 1 > /sys/class/power_supply/battery/input_suspend", false);
+ }
+
+ private synchronized void processBatteryChange(Intent intent) {
+ Bundle bundle = intent.getExtras();
+ int current = bundle.getInt("level");
+
+ if(alwaysConnected) { // Always connected overrides limit to 80
+ if(current < 40)
+ resetSuspendState();
+ else if(current > 60)
+ suspendCharger();
+ } else if(limitToEighty) {
+ if(current < 78)
+ resetSuspendState();
+ else if(current > 80)
+ suspendCharger();
+ } else {
+ resetSuspendState();
+ }
+ }
+}
diff --git a/CloverParts/src/io/alcatraz/cloverparts/BMSService.java b/CloverParts/src/io/alcatraz/cloverparts/BMSService.java
new file mode 100644
index 00000000..8b747081
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/BMSService.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+public class BMSService extends Service {
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ registerReceiver(new BMSReceiver(), intentFilter);
+ return super.onStartCommand(intent, flags, startId);
+ }
+}
diff --git a/CloverParts/src/io/alcatraz/cloverparts/BootReceiver.java b/CloverParts/src/io/alcatraz/cloverparts/BootReceiver.java
new file mode 100755
index 00000000..2dd543f1
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/BootReceiver.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2023, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class BootReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (context == null) {
+ return;
+ }
+
+ context.startService(new Intent(context, BMSService.class));
+ SharedPreferenceUtil sharedPreferenceUtil = SharedPreferenceUtil.getInstance();
+ boolean stepChargingManualOverride = (boolean) sharedPreferenceUtil.get(context, "bms_step_charging_switch",
+ true);
+
+ ShellUtils.execCommand("echo " + (stepChargingManualOverride ? "1" : "0") + " > /sys/class/power_supply/battery/step_charging_enabled", false);
+ ShellUtils.execCommand("echo " + (stepChargingManualOverride ? "1" : "0") + " > /sys/class/power_supply/battery/sw_jeita_enabled", false);
+ }
+}
diff --git a/CloverParts/src/io/alcatraz/cloverparts/Constants.java b/CloverParts/src/io/alcatraz/cloverparts/Constants.java
new file mode 100755
index 00000000..e96164f5
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/Constants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2021, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+public class Constants {
+ public static final String BMS_STEP_CHG_SWITCH = "bms_step_charging_switch";
+ public static final String BMS_ALWAYS_CONNECTED_MODE = "bms_always_connected_mode";
+ public static final String BMS_LIMIT_TO_EIGHTY = "bms_limit_to_eighty";
+}
diff --git a/CloverParts/src/io/alcatraz/cloverparts/SharedPreferenceUtil.java b/CloverParts/src/io/alcatraz/cloverparts/SharedPreferenceUtil.java
new file mode 100644
index 00000000..2c19cf45
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/SharedPreferenceUtil.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2021, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package io.alcatraz.cloverparts;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.annotation.Nullable;
+
+public final class SharedPreferenceUtil {
+
+ private static final String FILE_NAME = "io.alcatraz.cloverparts_preferences";
+ private static SharedPreferenceUtil mInstance;
+
+ public static SharedPreferenceUtil getInstance() {
+ if (mInstance == null) {
+ synchronized (SharedPreferenceUtil.class) {
+ if (mInstance == null) {
+ mInstance = new SharedPreferenceUtil();
+ }
+ }
+ }
+ return mInstance;
+ }
+
+ public boolean put(Context context, String key, Object value) {
+ String type = value.getClass().getSimpleName();
+ SharedPreferences sharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ if ("Integer".equals(type)) {
+ editor.putInt(key, (Integer) value);
+ } else if ("Boolean".equals(type)) {
+ editor.putBoolean(key, (Boolean) value);
+ } else if ("Float".equals(type)) {
+ editor.putFloat(key, (Float) value);
+ } else if ("Long".equals(type)) {
+ editor.putLong(key, (Long) value);
+ } else if ("String".equals(type)) {
+ editor.putString(key, (String) value);
+ }
+ editor.apply();
+ return false;
+ }
+
+ @Nullable
+ public Object get(Context context, String key, Object defValue) {
+ SharedPreferences sharedPreferences = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
+ String type = defValue.getClass().getSimpleName();
+ if ("Integer".equals(type)) {
+ return sharedPreferences.getInt(key, (Integer) defValue);
+ } else if ("Boolean".equals(type)) {
+ return sharedPreferences.getBoolean(key, (Boolean) defValue);
+ } else if ("Float".equals(type)) {
+ return sharedPreferences.getFloat(key, (Float) defValue);
+ } else if ("Long".equals(type)) {
+ return sharedPreferences.getLong(key, (Long) defValue);
+ } else if ("String".equals(type)) {
+ return sharedPreferences.getString(key, (String) defValue);
+ }
+ return null;
+ }
+}
diff --git a/CloverParts/src/io/alcatraz/cloverparts/ShellUtils.java b/CloverParts/src/io/alcatraz/cloverparts/ShellUtils.java
new file mode 100755
index 00000000..3b714abe
--- /dev/null
+++ b/CloverParts/src/io/alcatraz/cloverparts/ShellUtils.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2021, Alcatraz323
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+package io.alcatraz.cloverparts;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+
+public class ShellUtils {
+ public static final String ERR_EMPTY_COMMAND = "Command can't be null";
+
+ /**
+ * check whether has root permission
+ */
+ public static boolean hasRootPermission() {
+ return execCommand("echo root", true, false).result == 0;
+ }
+
+
+ public static CommandResult execCommand(String command, boolean isRoot) {
+ return execCommand(new String[]{command}, isRoot, true);
+ }
+
+ public static CommandResult execCommand(String command, boolean isRoot, boolean isNeedResultMsg) {
+
+ return execCommand(new String[]{command}, isRoot, isNeedResultMsg);
+ }
+
+ public static CommandResult execCommand(List commands, boolean isRoot, boolean isNeedResultMsg) {
+ return execCommand(commands == null ? null : commands.toArray(new String[]{}), isRoot, isNeedResultMsg);
+ }
+
+ /**
+ * execute shell commands
+ * {@link CommandResult#result} is -1, there maybe some excepiton.
+ *
+ * @param commands command array
+ * @param isRoot whether need to run with root
+ * @param needResponse whether need result msg
+ */
+ public static CommandResult execCommand(String[] commands, boolean isRoot, boolean needResponse) {
+
+ int result = -1;
+ if (commands == null || commands.length == 0) {
+ return new CommandResult(result, null, ERR_EMPTY_COMMAND);
+ }
+
+ Process process = null;
+ BufferedReader successResult = null;
+ BufferedReader errorResult = null;
+ StringBuilder successMsg = null;
+ StringBuilder errorMsg = null;
+
+ DataOutputStream os = null;
+ try {
+ process = Runtime.getRuntime().exec(isRoot ? COMMAND_SU : COMMAND_SH);
+ os = new DataOutputStream(process.getOutputStream());
+ for (String command : commands) {
+ if (command == null) {
+ continue;
+ }
+ // donnot use os.writeBytes(commmand), avoid chinese charset error
+ os.write(command.getBytes());
+ os.writeBytes(COMMAND_LINE_END);
+ os.flush();
+ }
+ os.writeBytes(COMMAND_EXIT);
+ os.flush();
+
+ result = process.waitFor();
+ if (needResponse) {
+ successMsg = new StringBuilder();
+ errorMsg = new StringBuilder();
+ successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
+ errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+ String s;
+ while ((s = successResult.readLine()) != null) {
+ successMsg.append(s).append("\n");
+ }
+ while ((s = errorResult.readLine()) != null) {
+ errorMsg.append(s);
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (errorResult != null) {
+ errorResult.close();
+ }
+ if (successResult != null) {
+ successResult.close();
+ }
+ if (os != null) {
+ os.close();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ if (process != null) {
+ process.destroy();
+ }
+ }
+
+ }
+ return new CommandResult(result, successMsg == null ? "" : successMsg.toString(), errorMsg == null ? ""
+ : errorMsg.toString());
+ }
+
+
+ public static class CommandResult {
+
+ public int result;
+ public String responseMsg;
+ public String errorMsg;
+
+ public CommandResult(int result) {
+ this.result = result;
+ }
+
+ public CommandResult(int result, String responseMsg, String errorMsg) {
+ this.result = result;
+ this.responseMsg = responseMsg;
+ this.errorMsg = errorMsg;
+ }
+ }
+
+ public static final String COMMAND_SU = "su";
+ public static final String COMMAND_SH = "sh";
+ public static final String COMMAND_EXIT = "exit\n";
+ public static final String COMMAND_LINE_END = "\n";
+
+}
diff --git a/sepolicy/private/cloverparts_app.te b/sepolicy/private/cloverparts_app.te
new file mode 100644
index 00000000..a7d1d5db
--- /dev/null
+++ b/sepolicy/private/cloverparts_app.te
@@ -0,0 +1,24 @@
+typeattribute cloverparts_app mlstrustedsubject;
+
+app_domain(cloverparts_app)
+
+allow cloverparts_app activity_service:service_manager find;
+allow cloverparts_app activity_task_service:service_manager find;
+allow cloverparts_app audio_service:service_manager find;
+allow cloverparts_app autofill_service:service_manager find;
+allow cloverparts_app content_capture_service:service_manager find;
+allow cloverparts_app gpu_service:service_manager find;
+allow cloverparts_app hint_service:service_manager find;
+allow cloverparts_app surfaceflinger_service:service_manager find;
+allow cloverparts_app game_service:service_manager find;
+allow cloverparts_app textservices_service:service_manager find;
+allow cloverparts_app netstats_service:service_manager find;
+allow cloverparts_app voiceinteraction_service:service_manager find;
+allow cloverparts_app batterystats_service:service_manager find;
+allow cloverparts_app batteryproperties_service:service_manager find;
+
+# ro.input.resampling, viewroot.profile_rendering
+dontaudit cloverparts_app default_prop:file read;
+
+allow cloverparts_app system_app_data_file:dir { create rw_dir_perms search open setattr };
+allow cloverparts_app system_app_data_file:file { create rw_file_perms rename setattr unlink };
diff --git a/sepolicy/private/seapp_contexts b/sepolicy/private/seapp_contexts
new file mode 100644
index 00000000..41025545
--- /dev/null
+++ b/sepolicy/private/seapp_contexts
@@ -0,0 +1 @@
+user=system seinfo=platform name=io.alcatraz.cloverparts domain=cloverparts_app type=system_app_data_file
\ No newline at end of file
diff --git a/sepolicy/public/cloverparts_app.te b/sepolicy/public/cloverparts_app.te
new file mode 100644
index 00000000..e46e6274
--- /dev/null
+++ b/sepolicy/public/cloverparts_app.te
@@ -0,0 +1 @@
+type cloverparts_app, domain;
\ No newline at end of file
diff --git a/sepolicy/vendor/cloverparts_app.te b/sepolicy/vendor/cloverparts_app.te
new file mode 100644
index 00000000..c85295eb
--- /dev/null
+++ b/sepolicy/vendor/cloverparts_app.te
@@ -0,0 +1,2 @@
+allow cloverparts_app sysfs_battery_supply:file rw_file_perms;
+allow cloverparts_app sysfs_battery_supply:dir search;
\ No newline at end of file