diff --git a/BoardConfig.mk b/BoardConfig.mk
new file mode 100644
index 0000000..081cb31
--- /dev/null
+++ b/BoardConfig.mk
@@ -0,0 +1,36 @@
+#
+# Copyright (C) 2021-2025 The LineageOS Project
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# Partitions
+BOARD_SUPER_PARTITION_SIZE := 13329498112
+
+# Include the common OEM chipset BoardConfig.
+include device/oneplus/sm8750-common/BoardConfigCommon.mk
+
+DEVICE_PATH := device/oneplus/dodge
+
+# Assert
+TARGET_OTA_ASSERT_DEVICE := OP5D0DL1,OP5D55L1
+
+# Display
+TARGET_SCREEN_DENSITY := 640
+
+# Kernel
+TARGET_KERNEL_ADDITIONAL_FLAGS += CONFIG_DODGE_DTB=y
+
+# Properties
+TARGET_ODM_PROP += $(DEVICE_PATH)/odm.prop
+TARGET_SYSTEM_EXT_PROP += $(DEVICE_PATH)/system_ext.prop
+TARGET_VENDOR_PROP += $(DEVICE_PATH)/vendor.prop
+
+# Recovery
+TARGET_RECOVERY_UI_MARGIN_HEIGHT := 103
+
+# SEPolicy
+BOARD_VENDOR_SEPOLICY_DIRS += device/oneplus/dodge/sepolicy/vendor
+
+# Include the proprietary files BoardConfig.
+include vendor/oneplus/dodge/BoardConfigVendor.mk
diff --git a/DeviceSettings/AndroidManifest.xml b/DeviceSettings/AndroidManifest.xml
index 2976355..f9e7cb4 100644
--- a/DeviceSettings/AndroidManifest.xml
+++ b/DeviceSettings/AndroidManifest.xml
@@ -111,6 +111,12 @@
android:resource="@string/bypass_charging_summary" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DeviceSettings/res/drawable/ic_custom_seekbar_minus.xml b/DeviceSettings/res/drawable/ic_custom_seekbar_minus.xml
new file mode 100644
index 0000000..a117dfe
--- /dev/null
+++ b/DeviceSettings/res/drawable/ic_custom_seekbar_minus.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/DeviceSettings/res/drawable/ic_custom_seekbar_plus.xml b/DeviceSettings/res/drawable/ic_custom_seekbar_plus.xml
new file mode 100644
index 0000000..e52c915
--- /dev/null
+++ b/DeviceSettings/res/drawable/ic_custom_seekbar_plus.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/DeviceSettings/res/drawable/ic_custom_seekbar_reset.xml b/DeviceSettings/res/drawable/ic_custom_seekbar_reset.xml
new file mode 100644
index 0000000..b527b47
--- /dev/null
+++ b/DeviceSettings/res/drawable/ic_custom_seekbar_reset.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
diff --git a/DeviceSettings/res/drawable/ic_waiting.xml b/DeviceSettings/res/drawable/ic_waiting.xml
new file mode 100644
index 0000000..b8306f3
--- /dev/null
+++ b/DeviceSettings/res/drawable/ic_waiting.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/DeviceSettings/res/layout/custom_seekbar_preference.xml b/DeviceSettings/res/layout/custom_seekbar_preference.xml
new file mode 100644
index 0000000..a2dd9c9
--- /dev/null
+++ b/DeviceSettings/res/layout/custom_seekbar_preference.xml
@@ -0,0 +1,150 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DeviceSettings/res/values/attrs.xml b/DeviceSettings/res/values/attrs.xml
new file mode 100644
index 0000000..c68738e
--- /dev/null
+++ b/DeviceSettings/res/values/attrs.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DeviceSettings/res/values/strings.xml b/DeviceSettings/res/values/strings.xml
index 2bc06dc..208720d 100644
--- a/DeviceSettings/res/values/strings.xml
+++ b/DeviceSettings/res/values/strings.xml
@@ -81,7 +81,20 @@
Bypass charging is not supported on this device
Enabled
Disabled
+<<<<<<< HEAD:DeviceSettings/res/values/strings.xml
Coudn\'t enable Bypass Charging.
+=======
+ It was not possible to change Bypass Charging status. Please, try again later.
+ Target level
+ Battery will charge until it reaches target level, then bypass charging will be activated.
+ Battery will charge until it reaches target level
+
+
+ Value: %s
+ by default
+ Default value: %s\nLong tap to set
+ Default value is set
+>>>>>>> 96046be (DeviceSettings: rework bypass charging):device-settings/res/values/strings.xml
GameBar
diff --git a/DeviceSettings/res/xml/bypass_charging_settings.xml b/DeviceSettings/res/xml/bypass_charging_settings.xml
index 1be278f..3989044 100644
--- a/DeviceSettings/res/xml/bypass_charging_settings.xml
+++ b/DeviceSettings/res/xml/bypass_charging_settings.xml
@@ -1,14 +1,35 @@
-
-
+
-
+
+
+
+
+
+
+
+
+
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/Constants.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/Constants.java
index 3f1b031..4e97f2d 100644
--- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/Constants.java
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/Constants.java
@@ -29,14 +29,14 @@ import android.media.AudioManager;
public class Constants {
- public static final String SLIDER_STATE
- = "/proc/tristatekey/tri_state";
+ /* Alert Slider */
+ public static final String NODE_SLIDER_STATE = "/proc/tristatekey/tri_state";
- public static final String NOTIF_SLIDER_PANEL_KEY = "notification_slider";
- public static final String NOTIF_SLIDER_USAGE_KEY = "slider_usage";
- public static final String NOTIF_SLIDER_ACTION_TOP_KEY = "action_top_position";
- public static final String NOTIF_SLIDER_ACTION_MIDDLE_KEY = "action_middle_position";
- public static final String NOTIF_SLIDER_ACTION_BOTTOM_KEY = "action_bottom_position";
+ public static final String KEY_NOTIF_SLIDER_PANEL = "notification_slider";
+ public static final String KEY_NOTIF_SLIDER_USAGE = "slider_usage";
+ public static final String KEY_NOTIF_SLIDER_ACTION_TOP = "action_top_position";
+ public static final String KEY_NOTIF_SLIDER_ACTION_MIDDLE = "action_middle_position";
+ public static final String KEY_NOTIF_SLIDER_ACTION_BOTTOM = "action_bottom_position";
public static final String EXTRA_SLIDER_USAGE = "usage";
public static final String EXTRA_SLIDER_ACTIONS = "actions";
@@ -76,4 +76,17 @@ public class Constants {
// Holds -> mapping
public static final Map sBooleanNodePreferenceMap = new HashMap<>();
public static final Map sStringNodePreferenceMap = new HashMap<>();
+
+ /* OnePulse PWM */
+ public static final String NODE_ONEPULSE_PWM = "/sys/kernel/oplus_display/pwm_onepulse";
+ public static final String KEY_ONEPULSE_PWM = "onepulse_pwm";
+
+ /* Bypass Charging */
+ public static final String NODE_BYPASS_CHARGING = "/sys/class/oplus_chg/battery/mmi_charging_enable";
+ public static final String KEY_BYPASS_CHARGING = "bypass_charging";
+ public static final String KEY_BYPASS_CHARGING_TARGET = "bypass_charging_target";
+
+ public static final int BYPASS_OFF = 0;
+ public static final int BYPASS_WAITING = 1;
+ public static final int BYPASS_ON = 2;
}
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java
index 73e0052..c76b2d4 100644
--- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java
@@ -98,17 +98,30 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
// mUSB2FastChargeModeSwitch.setEnabled(false);
// }
+<<<<<<< HEAD:DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java
+=======
+ mOnePulsePWMSwitch = (SwitchPreferenceCompat) findPreference(Constants.KEY_ONEPULSE_PWM);
+ if (Utils.fileWritable(Constants.NODE_ONEPULSE_PWM)) {
+ mOnePulsePWMSwitch.setEnabled(true);
+ mOnePulsePWMSwitch.setChecked(sharedPrefs.getBoolean(Constants.KEY_ONEPULSE_PWM,
+ Utils.getFileValueAsBoolean(Constants.NODE_ONEPULSE_PWM, false)));
+ mOnePulsePWMSwitch.setOnPreferenceChangeListener(this);
+ } else {
+ mOnePulsePWMSwitch.setEnabled(false);
+ }
+
+>>>>>>> 96046be (DeviceSettings: rework bypass charging):device-settings/src/org/lineageos/device/settings/DeviceSettings.java
initNotificationSliderPreference();
}
private void initNotificationSliderPreference() {
- registerPreferenceListener(Constants.NOTIF_SLIDER_USAGE_KEY);
- registerPreferenceListener(Constants.NOTIF_SLIDER_ACTION_TOP_KEY);
- registerPreferenceListener(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY);
- registerPreferenceListener(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY);
+ registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_USAGE);
+ registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_ACTION_TOP);
+ registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE);
+ registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM);
ListPreference usagePref = (ListPreference) findPreference(
- Constants.NOTIF_SLIDER_USAGE_KEY);
+ Constants.KEY_NOTIF_SLIDER_USAGE);
handleSliderUsageChange(usagePref.getValue());
}
@@ -138,18 +151,28 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
// Utils.writeValue(FILE_FAST_CHARGE, enabled ? "1" : "0");
// return true;
// }
+<<<<<<< HEAD:DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java
+=======
+ if (preference == mOnePulsePWMSwitch) {
+ boolean enabled = (Boolean) newValue;
+ SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ sharedPrefs.edit().putBoolean(Constants.KEY_ONEPULSE_PWM, enabled).commit();
+ Utils.writeValue(Constants.NODE_ONEPULSE_PWM, enabled ? "1" : "0");
+ return true;
+ }
+>>>>>>> 96046be (DeviceSettings: rework bypass charging):device-settings/src/org/lineageos/device/settings/DeviceSettings.java
String key = preference.getKey();
switch (key) {
- case Constants.NOTIF_SLIDER_USAGE_KEY:
+ case Constants.KEY_NOTIF_SLIDER_USAGE:
return handleSliderUsageChange((String) newValue) &&
handleSliderUsageDefaultsChange((String) newValue) &&
notifySliderUsageChange((String) newValue);
- case Constants.NOTIF_SLIDER_ACTION_TOP_KEY:
+ case Constants.KEY_NOTIF_SLIDER_ACTION_TOP:
return notifySliderActionChange(0, (String) newValue);
- case Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY:
+ case Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE:
return notifySliderActionChange(1, (String) newValue);
- case Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY:
+ case Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM:
return notifySliderActionChange(2, (String) newValue);
default:
break;
@@ -252,11 +275,11 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
private boolean updateSliderActions(int entriesResId, int entryValuesResId) {
String[] entries = getResources().getStringArray(entriesResId);
String[] entryValues = getResources().getStringArray(entryValuesResId);
- return updateSliderPreference(Constants.NOTIF_SLIDER_ACTION_TOP_KEY,
+ return updateSliderPreference(Constants.KEY_NOTIF_SLIDER_ACTION_TOP,
entries, entryValues) &&
- updateSliderPreference(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY,
+ updateSliderPreference(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE,
entries, entryValues) &&
- updateSliderPreference(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY,
+ updateSliderPreference(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM,
entries, entryValues);
}
@@ -266,11 +289,11 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
return false;
}
- return updateSliderPreferenceValue(Constants.NOTIF_SLIDER_ACTION_TOP_KEY,
+ return updateSliderPreferenceValue(Constants.KEY_NOTIF_SLIDER_ACTION_TOP,
defaults[0]) &&
- updateSliderPreferenceValue(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY,
+ updateSliderPreferenceValue(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE,
defaults[1]) &&
- updateSliderPreferenceValue(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY,
+ updateSliderPreferenceValue(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM,
defaults[2]);
}
@@ -300,15 +323,15 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
ListPreference p;
p = (ListPreference) findPreference(
- Constants.NOTIF_SLIDER_ACTION_TOP_KEY);
+ Constants.KEY_NOTIF_SLIDER_ACTION_TOP);
actions[0] = Integer.parseInt(p.getValue());
p = (ListPreference) findPreference(
- Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY);
+ Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE);
actions[1] = Integer.parseInt(p.getValue());
p = (ListPreference) findPreference(
- Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY);
+ Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM);
actions[2] = Integer.parseInt(p.getValue());
return actions;
@@ -322,7 +345,7 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
private boolean notifySliderActionChange(int index, String value) {
ListPreference p = (ListPreference) findPreference(
- Constants.NOTIF_SLIDER_USAGE_KEY);
+ Constants.KEY_NOTIF_SLIDER_USAGE);
int usage = Integer.parseInt(p.getValue());
int[] actions = getCurrentSliderActions();
@@ -348,7 +371,7 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
SharedPreferences prefs = context.getSharedPreferences(
context.getPackageName() + "_preferences", Context.MODE_PRIVATE);
- String usage = prefs.getString(Constants.NOTIF_SLIDER_USAGE_KEY,
+ String usage = prefs.getString(Constants.KEY_NOTIF_SLIDER_USAGE,
res.getString(R.string.config_defaultNotificationSliderUsage));
int defaultsResId = getDefaultResIdForUsage(usage);
@@ -362,19 +385,19 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
}
String actionTop = prefs.getString(
- Constants.NOTIF_SLIDER_ACTION_TOP_KEY, defaults[0]);
+ Constants.KEY_NOTIF_SLIDER_ACTION_TOP, defaults[0]);
String actionMiddle = prefs.getString(
- Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY, defaults[1]);
+ Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE, defaults[1]);
String actionBottom = prefs.getString(
- Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY, defaults[2]);
+ Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM, defaults[2]);
prefs.edit()
- .putString(Constants.NOTIF_SLIDER_USAGE_KEY, usage)
- .putString(Constants.NOTIF_SLIDER_ACTION_TOP_KEY, actionTop)
- .putString(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY, actionMiddle)
- .putString(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY, actionBottom)
+ .putString(Constants.KEY_NOTIF_SLIDER_USAGE, usage)
+ .putString(Constants.KEY_NOTIF_SLIDER_ACTION_TOP, actionTop)
+ .putString(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE, actionMiddle)
+ .putString(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM, actionBottom)
.commit();
sendUpdateBroadcast(context, Integer.parseInt(usage), new int[] {
@@ -393,6 +416,18 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
// }
// }
+<<<<<<< HEAD:DeviceSettings/src/org/lineageos/device/DeviceSettings/DeviceSettings.java
+=======
+ public static void restoreOnePulsePwmSetting(Context context) {
+ if (Utils.fileWritable(Constants.NODE_ONEPULSE_PWM)) {
+ SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean value = sharedPrefs.getBoolean(Constants.KEY_ONEPULSE_PWM,
+ Utils.getFileValueAsBoolean(Constants.NODE_ONEPULSE_PWM, false));
+ Utils.writeValue(Constants.NODE_ONEPULSE_PWM, value ? "1" : "0");
+ }
+ }
+
+>>>>>>> 96046be (DeviceSettings: rework bypass charging):device-settings/src/org/lineageos/device/settings/DeviceSettings.java
private static int getDefaultResIdForUsage(String usage) {
switch (usage) {
case Constants.NOTIF_SLIDER_FOR_NOTIFICATION:
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/SliderControllerBase.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/SliderControllerBase.java
index 80da35b..d5ebdf1 100644
--- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/SliderControllerBase.java
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/SliderControllerBase.java
@@ -80,7 +80,7 @@ public abstract class SliderControllerBase {
}
try {
- int state = Integer.parseInt(FileUtils.readOneLine(Constants.SLIDER_STATE).trim());
+ int state = Integer.parseInt(FileUtils.readOneLine(Constants.NODE_SLIDER_STATE).trim());
ret = processAction(mActions[state - 1]);
if (ret > 0 && notify) {
sendUpdateBroadcast(context, state - 1, ret);
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java
index af62ab6..ac197ea 100644
--- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingController.java
@@ -17,53 +17,30 @@
package org.lineageos.device.DeviceSettings.bypasschrg;
-import static lineageos.health.HealthInterface.MODE_AUTO;
-import static lineageos.health.HealthInterface.MODE_LIMIT;
-
import android.content.ContentResolver;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.provider.Settings;
+import android.os.BatteryManager;
import android.util.Log;
import android.widget.Toast;
import androidx.preference.PreferenceManager;
+import org.lineageos.device.DeviceSettings.Constants;
import org.lineageos.device.DeviceSettings.R;
import org.lineageos.device.DeviceSettings.utils.FileUtils;
-/**
- * This class is implemented to coexist with Lineage Charging Control (CC).
- * Bypass Charging will override (disable) CC, while it's enabled.
- * CC status will be restored, when Bypass Charging is disabled.
- * Any user changes to CC settings, while Bypass Charging is enabled,
- * will override Bypass Charging settings.
- */
public class BypassChargingController {
- private static final boolean DEBUG = false;
-
+ private static final boolean DEBUG = true;
private static final String TAG = "BypassChargingController";
- private static final String BYPASS_CHARGING_NODE = "/sys/class/oplus_chg/battery/mmi_charging_enable";
- private static final String KEY_BYPASS_CHARGING_ENABLED = "bypass_charging_enabled";
-
- // Bypass modes
private static final String BYPASS_CHARGING_ENABLED = "0";
private static final String BYPASS_CHARGING_DISABLED = "1";
+ private static final String KEY_BATTERY_LEVEL = "current_battery_level";
- private static final int CC_LIMIT_MIN = 10;
- private static final int CC_LIMIT_MAX = 100;
- private static final int CC_LIMIT_DEF = 80;
-
- // Charging Control settings
- private static final String KEY_CHARGING_CONTROL_ENABLED = "charging_control_enabled";
- private static final String KEY_CHARGING_CONTROL_MODE = "charging_control_mode";
- private static final String KEY_CHARGING_CONTROL_LIMIT = "charging_control_charging_limit";
+ private int mBatteryLevel;
private Context mContext;
private ContentResolver mContentResolver;
+ private final Object mLock = new Object();
private static BypassChargingController sInstance;
public static synchronized BypassChargingController getInstance(Context context) {
@@ -76,37 +53,45 @@ public class BypassChargingController {
private BypassChargingController(Context context) {
mContext = context.getApplicationContext();
mContentResolver = mContext.getContentResolver();
+ mBatteryLevel = getLevelFromIntent();
+ if (isValidLevel(mBatteryLevel)) {
+ saveCurrentBatteryLevel(mBatteryLevel);
+ }
}
- private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- switch(uri.getLastPathSegment()) {
- case KEY_CHARGING_CONTROL_ENABLED:
- case KEY_CHARGING_CONTROL_MODE:
- case KEY_CHARGING_CONTROL_LIMIT:
- break;
+ // Called from Service when battery level changes
+ public void onBatteryLevelChanged(int level) {
+ synchronized (mLock) {
+ if (isValidLevel(level) && (mBatteryLevel == -1 || mBatteryLevel != level)) {
+ mBatteryLevel = level;
+ saveCurrentBatteryLevel(level);
+ maybeEnableBypassCharging();
+ if (DEBUG) Log.d(TAG, "Battery level changed (service): " + level + "%");
}
}
- };
-
- public boolean isBypassChargingSupported() {
- return isNodeAccessible(BYPASS_CHARGING_NODE);
}
- public boolean isBypassChargingEnabled() {
- try {
- String value = FileUtils.readOneLine(BYPASS_CHARGING_NODE);
- return value != null && BYPASS_CHARGING_ENABLED.equals(value);
- } catch (Exception e) {
- Log.e(TAG, "Failed to read bypass sysnode", e);
- return false;
+ // get battery level using a sticky intent
+ private int getLevelFromIntent() {
+ android.content.IntentFilter filter =
+ new android.content.IntentFilter(android.content.Intent.ACTION_BATTERY_CHANGED);
+ android.content.Intent intent = mContext.registerReceiver(null, filter);
+
+ if (intent == null) {
+ if (DEBUG) Log.w(TAG, "Sticky battery intent was null");
+ return -1;
}
+
+ int extraLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
+ int extraScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
+
+ return (extraLevel >= 0 && extraScale > 0) ?
+ (int)((extraLevel / (float)extraScale) * 100) : -1;
}
private boolean isNodeAccessible(String node) {
try {
- String value = FileUtils.readOneLine(node);
+ String status = FileUtils.readOneLine(node);
return true;
} catch (Exception e) {
Log.e(TAG, "Node " + node + " not accessible", e);
@@ -114,102 +99,146 @@ public class BypassChargingController {
}
}
- private boolean writeToNode(String value) {
- try {
- FileUtils.writeLine(BYPASS_CHARGING_NODE, value);
- } catch (Exception e) {
- Log.e(TAG, "Failed to write bypass sysnode", e);
+ private boolean writeToNode(String status) {
+ synchronized (mLock) {
+ try {
+ FileUtils.writeLine(Constants.NODE_BYPASS_CHARGING, status);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to write bypass sysnode", e);
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private int readFromNode() {
+ synchronized (mLock) {
+ try {
+ String value = FileUtils.readOneLine(Constants.NODE_BYPASS_CHARGING);
+ return Integer.parseInt(value);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to read bypass sysnode", e);
+ return -1;
+ }
+ }
+ }
+
+ public boolean isBypassChargingSupported() {
+ return isNodeAccessible(Constants.NODE_BYPASS_CHARGING);
+ }
+
+ private void maybeEnableBypassCharging() {
+ if (mBatteryLevel >= getBypassChargingTarget()
+ && getBypassChargingStatus() == Constants.BYPASS_WAITING) {
+ if (writeToNode(BYPASS_CHARGING_ENABLED)) {
+ saveBypassChargingStatus(Constants.BYPASS_ON);
+ BypassChargingService.stop(mContext);
+ }
+ }
+ }
+
+ private void maybeDisableBypassCharging(int target) {
+ if (mBatteryLevel < target
+ && getBypassChargingStatus() == Constants.BYPASS_ON) {
+ if (writeToNode(BYPASS_CHARGING_DISABLED)) {
+ saveBypassChargingStatus(Constants.BYPASS_WAITING);
+ BypassChargingService.start(mContext);
+ }
+ }
+ }
+
+ public boolean enableBypassCharging() {
+ int level = getCurrentBatteryLevel();
+
+ if (!isValidLevel(level)) {
+ if (DEBUG) Log.w(TAG, "Cannot enable bypass: invalid battery level " + level);
return false;
}
- return true;
- }
- public void setBypassCharging(boolean enable) {
- if (enable) {
- enableBypassCharging();
+ if (getBypassChargingTarget() > level) {
+ saveBypassChargingStatus(Constants.BYPASS_WAITING);
+ BypassChargingService.start(mContext);
+ return true;
}
- else {
- disableBypassCharging();
+ else if (writeToNode(BYPASS_CHARGING_ENABLED)) {
+ saveBypassChargingStatus(Constants.BYPASS_ON);
+ BypassChargingService.stop(mContext);
+ return true;
}
+ return false;
}
- private void enableBypassCharging() {
- setChargingControlEnabled(true);
- setChargingControlMode(MODE_LIMIT);
- setChargingControlLimit(CC_LIMIT_MIN);
- writeToNode(BYPASS_CHARGING_ENABLED);
+ public boolean disableBypassCharging() {
+ if (writeToNode(BYPASS_CHARGING_DISABLED)) {
+ saveBypassChargingStatus(Constants.BYPASS_OFF);
+ BypassChargingService.stop(mContext);
+ return true;
+ }
+ return false;
}
- public void disableBypassCharging() {
- writeToNode(BYPASS_CHARGING_DISABLED);
- setChargingControlLimit(CC_LIMIT_DEF);
- // setChargingControlMode(MODE_AUTO);
- setChargingControlEnabled(false);
- }
-
- private void saveBypassChargingEnabled(boolean enabled) {
+ private void saveBypassChargingStatus(int status) {
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
- .putBoolean(KEY_BYPASS_CHARGING_ENABLED, enabled)
- .commit();
+ .putInt(Constants.KEY_BYPASS_CHARGING, status)
+ .apply();
}
- private boolean isSavedBypassChargingEnabled() {
+ public int getBypassChargingStatus() {
return PreferenceManager.getDefaultSharedPreferences(mContext)
- .getBoolean(KEY_BYPASS_CHARGING_ENABLED, false);
+ .getInt(Constants.KEY_BYPASS_CHARGING, Constants.BYPASS_OFF);
}
- private void backupChargingControlSettings() {
+ public void setBypassChargingTarget(int target) {
+ if (target >= 0 && target <= 100) {
+ saveBypassChargingTarget(target);
+ if (getBypassChargingStatus() == Constants.BYPASS_ON) {
+ maybeDisableBypassCharging(target);
+ }
+ else {
+ maybeEnableBypassCharging();
+ }
+ }
+ }
+
+ private void saveBypassChargingTarget(int target) {
PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
- .putInt(KEY_CHARGING_CONTROL_MODE, getChargingControlMode())
- .putInt(KEY_CHARGING_CONTROL_LIMIT, getChargingControlLimit())
- .putBoolean(KEY_CHARGING_CONTROL_ENABLED, isChargingControlEnabled())
- .commit();
+ .putInt(Constants.KEY_BYPASS_CHARGING_TARGET, target)
+ .apply();
}
- private void restoreChargingControlSettings() {
- SharedPreferences sharedPreferences =
- PreferenceManager.getDefaultSharedPreferences(mContext);
- setChargingControlMode(sharedPreferences.getInt(
- KEY_CHARGING_CONTROL_LIMIT, CC_LIMIT_DEF));
- setChargingControlMode(sharedPreferences.getInt(
- KEY_CHARGING_CONTROL_MODE, MODE_AUTO));
- setChargingControlEnabled(sharedPreferences.getBoolean(
- KEY_CHARGING_CONTROL_ENABLED, false));
+ public int getBypassChargingTarget() {
+ return PreferenceManager.getDefaultSharedPreferences(mContext)
+ .getInt(Constants.KEY_BYPASS_CHARGING_TARGET, 0);
}
- private boolean isChargingControlEnabled() {
- return Settings.System.getInt(mContentResolver,
- KEY_CHARGING_CONTROL_ENABLED, 0) != 0;
- }
-
- private void setChargingControlEnabled(boolean enabled) {
- Settings.System.putInt(mContentResolver,
- KEY_CHARGING_CONTROL_ENABLED, enabled ? 1 : 0);
- }
-
- private int getChargingControlMode() {
- return Settings.System.getInt(mContentResolver,
- KEY_CHARGING_CONTROL_MODE, MODE_AUTO);
- }
-
- private void setChargingControlMode(int mode) {
- Settings.System.putInt(mContentResolver,
- KEY_CHARGING_CONTROL_MODE, mode);
- }
-
- private int getChargingControlLimit() {
- return Settings.System.getInt(mContentResolver,
- KEY_CHARGING_CONTROL_LIMIT, CC_LIMIT_DEF);
- }
-
- private void setChargingControlLimit(int limit) {
- if (limit < CC_LIMIT_MIN || limit > CC_LIMIT_MAX) {
- return;
+ private void saveCurrentBatteryLevel(int level) {
+ synchronized (mLock) {
+ if (isValidLevel(level)) {
+ PreferenceManager.getDefaultSharedPreferences(mContext)
+ .edit()
+ .putInt(KEY_BATTERY_LEVEL, level)
+ .apply();
+ } else {
+ if (DEBUG) Log.w(TAG, "Attempted to save invalid battery level: " + level);
+ }
}
- Settings.System.putInt(mContentResolver,
- KEY_CHARGING_CONTROL_LIMIT, limit);
+ }
+
+ public int getCurrentBatteryLevel() {
+ synchronized (mLock) {
+ int level = PreferenceManager.getDefaultSharedPreferences(mContext)
+ .getInt(KEY_BATTERY_LEVEL, -1);
+ if (!isValidLevel(level)) {
+ if (DEBUG) Log.w(TAG, "Battery level is invalid or not set: " + level);
+ }
+ return level;
+ }
+ }
+
+ private boolean isValidLevel(int level) {
+ return level >= 0 && level <= 100;
}
private void showToast(int resId) {
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java
index 4c5f460..3c68296 100644
--- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingFragment.java
@@ -21,12 +21,12 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.TwoStatePreference;
+import org.lineageos.device.DeviceSettings.Constants;
import org.lineageos.device.DeviceSettings.R;
+import org.lineageos.device.DeviceSettings.preferences.CustomSeekBarPreference;
public class BypassChargingFragment extends PreferenceFragmentCompat {
- private static final String KEY_BYPASS_CHARGING = "bypass_charging";
-
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.bypass_charging_settings, rootKey);
@@ -35,16 +35,33 @@ public class BypassChargingFragment extends PreferenceFragmentCompat {
BypassChargingController.getInstance(getContext());
boolean bypassSupported = bypassController.isBypassChargingSupported();
- TwoStatePreference bypassPreference = findPreference(KEY_BYPASS_CHARGING);
- bypassPreference.setEnabled(bypassSupported);
- if (bypassSupported) {
- bypassPreference.setChecked(bypassController.isBypassChargingEnabled());
- bypassPreference.setOnPreferenceChangeListener((pref, newValue) -> {
- bypassController.setBypassCharging((boolean) newValue);
- return true;
- });
- } else {
- bypassPreference.setSummary(R.string.bypass_charging_unavailable);
+ TwoStatePreference bypassPreference = findPreference(Constants.KEY_BYPASS_CHARGING);
+ if (bypassPreference != null) {
+ bypassPreference.setEnabled(bypassSupported);
+ if (bypassSupported) {
+ bypassPreference.setChecked(bypassController.getBypassChargingStatus() != Constants.BYPASS_OFF);
+ bypassPreference.setOnPreferenceChangeListener((pref, newValue) -> {
+ boolean enable = (boolean) newValue;
+ if (enable) {
+ bypassController.enableBypassCharging();
+ }
+ else {
+ bypassController.disableBypassCharging();
+ }
+ return true;
+ });
+ } else {
+ bypassPreference.setSummary(R.string.bypass_charging_unavailable);
+ }
+ }
+
+ CustomSeekBarPreference targetPreference = findPreference(Constants.KEY_BYPASS_CHARGING_TARGET);
+ if (targetPreference != null) {
+ targetPreference.setValue(bypassController.getBypassChargingTarget());
+ targetPreference.setOnPreferenceChangeListener((pref, newValue) -> {
+ bypassController.setBypassChargingTarget((int) newValue);
+ return true;
+ });
}
}
}
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingService.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingService.java
new file mode 100644
index 0000000..4d387ac
--- /dev/null
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingService.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+package org.lineageos.device.DeviceSettings.bypasschrg;
+
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_SCALE;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+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.os.Build;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.core.app.NotificationCompat;
+
+import org.lineageos.device.DeviceSettings.Constants;
+import org.lineageos.device.DeviceSettings.R;
+
+public class BypassChargingService extends Service {
+ public static final String CHANNEL_ID = "bypass_charging_channel";
+ public static final int NOTIF_ID = 1337;
+ private static final String TAG = "BypassChargingService";
+
+ private BypassChargingController mController;
+ private BroadcastReceiver mBatteryReceiver;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mController = BypassChargingController.getInstance(getApplicationContext());
+ createNotificationChannel();
+ startForeground(NOTIF_ID, buildNotification());
+ registerBatteryReceiver();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ unregisterBatteryReceiver();
+ stopForeground(true);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null; // Not a bound service
+ }
+
+ private void registerBatteryReceiver() {
+ if (mBatteryReceiver != null) return;
+ mBatteryReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ int extraLevel = intent.getIntExtra(EXTRA_LEVEL, -1);
+ int extraScale = intent.getIntExtra(EXTRA_SCALE, -1);
+ int level = (extraLevel >= 0 && extraScale > 0) ?
+ (int) ((extraLevel / (float) extraScale) * 100) : -1;
+
+ Log.d(TAG, "Battery level in service: " + level);
+ mController.onBatteryLevelChanged(level);
+ }
+ };
+ IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+ registerReceiver(mBatteryReceiver, filter);
+ }
+
+ private void unregisterBatteryReceiver() {
+ if (mBatteryReceiver != null) {
+ try {
+ unregisterReceiver(mBatteryReceiver);
+ } catch (Exception e) {}
+ mBatteryReceiver = null;
+ }
+ }
+
+ private void createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL_ID,
+ getString(R.string.bypass_charging_title),
+ NotificationManager.IMPORTANCE_LOW
+ );
+ channel.setDescription(getString(R.string.bypass_charging_waiting));
+ NotificationManager manager = getSystemService(NotificationManager.class);
+ if (manager != null) manager.createNotificationChannel(channel);
+ }
+ }
+
+ private Notification buildNotification() {
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle(getString(R.string.bypass_charging_title))
+ .setContentText(getString(R.string.bypass_charging_waiting))
+ .setSmallIcon(R.drawable.ic_bypass_charging)
+ .setOngoing(true)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+ return builder.build();
+ }
+
+ // Static method to start/stop service from anywhere
+ public static void start(Context context) {
+ Intent intent = new Intent(context, BypassChargingService.class);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundServiceAsUser(intent, UserHandle.CURRENT);
+ } else {
+ context.startService(intent);
+ }
+ }
+
+ public static void stop(Context context) {
+ Intent intent = new Intent(context, BypassChargingService.class);
+ context.stopService(intent);
+ }
+}
diff --git a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java
index 6dce7b7..43c85dc 100644
--- a/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java
+++ b/DeviceSettings/src/org/lineageos/device/DeviceSettings/bypasschrg/BypassChargingTile.java
@@ -16,9 +16,12 @@
package org.lineageos.device.DeviceSettings.bypasschrg;
+import android.graphics.drawable.Icon;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
+
+import org.lineageos.device.DeviceSettings.Constants;
import org.lineageos.device.DeviceSettings.R;
public class BypassChargingTile extends TileService {
@@ -35,26 +38,42 @@ public class BypassChargingTile extends TileService {
@Override
public void onStartListening() {
- mEnabled = mBypassController.isBypassChargingEnabled();
- updateTileState();
+ int status = mBypassController.getBypassChargingStatus();
+ mEnabled = status != Constants.BYPASS_OFF;
+ updateTileState(status);
}
@Override
public void onClick() {
- if (mEnabled == mBypassController.isBypassChargingEnabled()) {
- mEnabled = !mEnabled;
- updateTileState();
- mBypassController.setBypassCharging(mEnabled);
+ boolean enabled = mBypassController.getBypassChargingStatus() != Constants.BYPASS_OFF;
+ if (mEnabled == enabled) {
+ boolean success;
+ if (mEnabled) {
+ success = mBypassController.disableBypassCharging() ? true : false;
+ }
+ else {
+ success = mBypassController.enableBypassCharging() ? true : false;
+ }
+ if (success) {
+ mEnabled = !mEnabled;
+ updateTileState(mBypassController.getBypassChargingStatus());
+ }
}
}
- private void updateTileState() {
+ private void updateTileState(int status) {
Tile tile = getQsTile();
if (tile == null) return;
- tile.setState(mEnabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
+ tile.setState(status==Constants.BYPASS_OFF ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE);
tile.setLabel(getString(R.string.bypass_charging_title));
tile.setContentDescription(getString(R.string.bypass_charging_summary));
+ if (status==Constants.BYPASS_WAITING) {
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_bypass_waiting));
+ }
+ else {
+ tile.setIcon(Icon.createWithResource(this, R.drawable.ic_bypass_charging));
+ }
tile.updateTile();
}
}
diff --git a/DeviceSettings/src/org/lineageos/device/settings/preferences/CustomSeekBarPreference.java b/DeviceSettings/src/org/lineageos/device/settings/preferences/CustomSeekBarPreference.java
new file mode 100644
index 0000000..26fa30c
--- /dev/null
+++ b/DeviceSettings/src/org/lineageos/device/settings/preferences/CustomSeekBarPreference.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016-2023 crDroid Android 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.
+ */
+package org.lineageos.device.settings.preferences;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.*;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.lineageos.device.DeviceSettings.R;
+import com.android.settingslib.Utils;
+
+public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {
+ protected final String TAG = getClass().getName();
+ private static final String SETTINGS_NS = "http://schemas.android.com/apk/res/com.android.settings";
+ protected static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
+
+ protected int mInterval = 1;
+ protected boolean mShowSign = false;
+ protected String mUnits = "";
+ protected boolean mContinuousUpdates = false;
+
+ protected int mMinValue = 0;
+ protected int mMaxValue = 100;
+ protected boolean mDefaultValueExists = false;
+ protected int mDefaultValue;
+ protected boolean mDefaultValueTextExists = false;
+ protected String mDefaultValueText;
+
+ protected int mValue;
+
+ protected TextView mValueTextView;
+ protected ImageView mResetImageView;
+ protected ImageView mMinusImageView;
+ protected ImageView mPlusImageView;
+ protected SeekBar mSeekBar;
+
+ protected boolean mTrackingTouch = false;
+ protected int mTrackingValue;
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference);
+ try {
+ mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, mShowSign);
+ String units = a.getString(R.styleable.CustomSeekBarPreference_units);
+ if (units != null)
+ mUnits = " " + units;
+ mContinuousUpdates = a.getBoolean(R.styleable.CustomSeekBarPreference_continuousUpdates, mContinuousUpdates);
+ String defaultValueText = a.getString(R.styleable.CustomSeekBarPreference_defaultValueText);
+ mDefaultValueTextExists = defaultValueText != null && !defaultValueText.isEmpty();
+ if (mDefaultValueTextExists) {
+ mDefaultValueText = defaultValueText;
+ }
+ } finally {
+ a.recycle();
+ }
+
+ try {
+ String newInterval = attrs.getAttributeValue(SETTINGS_NS, "interval");
+ if (newInterval != null)
+ mInterval = Integer.parseInt(newInterval);
+ } catch (Exception e) {
+ Log.e(TAG, "Invalid interval value", e);
+ }
+ mMinValue = attrs.getAttributeIntValue(SETTINGS_NS, "min", mMinValue);
+ mMaxValue = attrs.getAttributeIntValue(ANDROIDNS, "max", mMaxValue);
+ if (mMaxValue < mMinValue)
+ mMaxValue = mMinValue;
+ String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue");
+ mDefaultValueExists = defaultValue != null && !defaultValue.isEmpty();
+ if (mDefaultValueExists) {
+ mDefaultValue = getLimitedValue(Integer.parseInt(defaultValue));
+ mValue = mDefaultValue;
+ } else {
+ mValue = mMinValue;
+ }
+
+ mSeekBar = new SeekBar(context, attrs);
+ setLayoutResource(R.layout.custom_seekbar_preference);
+ }
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.seekBarPreferenceStyle,
+ com.android.internal.R.attr.seekBarPreferenceStyle));
+ }
+
+ public CustomSeekBarPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onDependencyChanged(Preference dependency, boolean disableDependent) {
+ super.onDependencyChanged(dependency, disableDependent);
+ this.setShouldDisableView(true);
+ if (mSeekBar != null)
+ mSeekBar.setEnabled(!disableDependent);
+ if (mResetImageView != null)
+ mResetImageView.setEnabled(!disableDependent);
+ if (mPlusImageView != null)
+ mPlusImageView.setEnabled(!disableDependent);
+ if (mMinusImageView != null)
+ mMinusImageView.setEnabled(!disableDependent);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ try
+ {
+ // move our seekbar to the new view we've been given
+ ViewParent oldContainer = mSeekBar.getParent();
+ ViewGroup newContainer = (ViewGroup) holder.findViewById(R.id.seekbar);
+ if (oldContainer != newContainer) {
+ // remove the seekbar from the old view
+ if (oldContainer != null) {
+ ((ViewGroup) oldContainer).removeView(mSeekBar);
+ }
+ // remove the existing seekbar (there may not be one) and add ours
+ newContainer.removeAllViews();
+ newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Error binding view: " + ex.toString());
+ }
+
+ mSeekBar.setMax(getSeekValue(mMaxValue));
+ mSeekBar.setProgress(getSeekValue(mValue));
+ mSeekBar.setEnabled(isEnabled());
+
+ mValueTextView = (TextView) holder.findViewById(R.id.value);
+ mResetImageView = (ImageView) holder.findViewById(R.id.reset);
+ mMinusImageView = (ImageView) holder.findViewById(R.id.minus);
+ mPlusImageView = (ImageView) holder.findViewById(R.id.plus);
+
+ updateValueViews();
+
+ mSeekBar.setOnSeekBarChangeListener(this);
+ mResetImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Toast.makeText(getContext(), getContext().getString(R.string.custom_seekbar_default_value_to_set, getTextValue(mDefaultValue)),
+ Toast.LENGTH_LONG).show();
+ }
+ });
+ mResetImageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ setValue(mDefaultValue, true);
+ return true;
+ }
+ });
+ mMinusImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setValue(mValue - mInterval, true);
+ }
+ });
+ mMinusImageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue < mValue * 2 ? Math.floorDiv(mMaxValue + mMinValue, 2) : mMinValue, true);
+ return true;
+ }
+ });
+ mPlusImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setValue(mValue + mInterval, true);
+ }
+ });
+ mPlusImageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue > mValue * 2 ? -1 * Math.floorDiv(-1 * (mMaxValue + mMinValue), 2) : mMaxValue, true);
+ return true;
+ }
+ });
+ }
+
+ protected int getLimitedValue(int v) {
+ return v < mMinValue ? mMinValue : (v > mMaxValue ? mMaxValue : v);
+ }
+
+ protected int getSeekValue(int v) {
+ return 0 - Math.floorDiv(mMinValue - v, mInterval);
+ }
+
+ protected String getTextValue(int v) {
+ if (mDefaultValueTextExists && mDefaultValueExists && v == mDefaultValue) {
+ return mDefaultValueText;
+ }
+ return (mShowSign && v > 0 ? "+" : "") + String.valueOf(v) + mUnits;
+ }
+
+ protected void updateValueViews() {
+ if (mValueTextView != null) {
+ if (!mTrackingTouch || mContinuousUpdates) {
+ if (mDefaultValueTextExists && mDefaultValueExists && mValue == mDefaultValue) {
+ mValueTextView.setText(mDefaultValueText + " (" +
+ getContext().getString(R.string.custom_seekbar_default_value) + ")");
+ } else {
+ mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, getTextValue(mValue)) +
+ (mDefaultValueExists && mValue == mDefaultValue ? " (" +
+ getContext().getString(R.string.custom_seekbar_default_value) + ")" : ""));
+ }
+ } else {
+ if (mDefaultValueTextExists && mDefaultValueExists && mTrackingValue == mDefaultValue) {
+ mValueTextView.setText("[" + mDefaultValueText + "]");
+ } else {
+ mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, "[" + getTextValue(mTrackingValue) + "]"));
+ }
+ }
+ }
+ if (mResetImageView != null) {
+ if (!mDefaultValueExists || mValue == mDefaultValue || mTrackingTouch)
+ mResetImageView.setVisibility(View.INVISIBLE);
+ else
+ mResetImageView.setVisibility(View.VISIBLE);
+ }
+ if (mMinusImageView != null) {
+ if (mValue == mMinValue || mTrackingTouch) {
+ mMinusImageView.setClickable(false);
+ mMinusImageView.setColorFilter(Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorTertiary),
+ PorterDuff.Mode.SRC_IN);
+ } else {
+ mMinusImageView.setClickable(true);
+ mMinusImageView.clearColorFilter();
+ }
+ }
+ if (mPlusImageView != null) {
+ if (mValue == mMaxValue || mTrackingTouch) {
+ mPlusImageView.setClickable(false);
+ mPlusImageView.setColorFilter(Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorTertiary),
+ PorterDuff.Mode.SRC_IN);
+ } else {
+ mPlusImageView.setClickable(true);
+ mPlusImageView.clearColorFilter();
+ }
+ }
+ }
+
+ protected void changeValue(int newValue) {
+ // for subclasses
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ int newValue = getLimitedValue(mMinValue + (progress * mInterval));
+ if (mTrackingTouch && !mContinuousUpdates) {
+ mTrackingValue = newValue;
+ updateValueViews();
+ } else if (mValue != newValue) {
+ // change rejected, revert to the previous value
+ if (!callChangeListener(newValue)) {
+ mSeekBar.setProgress(getSeekValue(mValue));
+ return;
+ }
+ // change accepted, store it
+ changeValue(newValue);
+ persistInt(newValue);
+
+ mValue = newValue;
+ updateValueViews();
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mTrackingValue = mValue;
+ mTrackingTouch = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mTrackingTouch = false;
+ if (!mContinuousUpdates)
+ onProgressChanged(mSeekBar, getSeekValue(mTrackingValue), false);
+ notifyChanged();
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ if (restoreValue)
+ mValue = getPersistedInt(mValue);
+ }
+
+ @Override
+ public void setDefaultValue(Object defaultValue) {
+ if (defaultValue instanceof Integer)
+ setDefaultValue((Integer) defaultValue, mSeekBar != null);
+ else
+ setDefaultValue(defaultValue == null ? (String) null : defaultValue.toString(), mSeekBar != null);
+ }
+
+ public void setDefaultValue(int newValue, boolean update) {
+ newValue = getLimitedValue(newValue);
+ if (!mDefaultValueExists || mDefaultValue != newValue) {
+ mDefaultValueExists = true;
+ mDefaultValue = newValue;
+ if (update)
+ updateValueViews();
+ }
+ }
+
+ public void setDefaultValue(String newValue, boolean update) {
+ if (mDefaultValueExists && (newValue == null || newValue.isEmpty())) {
+ mDefaultValueExists = false;
+ if (update)
+ updateValueViews();
+ } else if (newValue != null && !newValue.isEmpty()) {
+ setDefaultValue(Integer.parseInt(newValue), update);
+ }
+ }
+
+ public void setValue(int newValue) {
+ mValue = getLimitedValue(newValue);
+ if (mSeekBar != null) mSeekBar.setProgress(getSeekValue(mValue));
+ }
+
+ public void setValue(int newValue, boolean update) {
+ newValue = getLimitedValue(newValue);
+ if (mValue != newValue) {
+ if (update)
+ mSeekBar.setProgress(getSeekValue(newValue));
+ else
+ mValue = newValue;
+ }
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public void setMax(int max) {
+ mMaxValue = max < mMinValue ? mMinValue : max;
+ mSeekBar.setMax(mMaxValue);
+ }
+
+ public void setMin(int min) {
+ mMinValue = min > mMaxValue ? mMaxValue : min;
+ mSeekBar.setMin(mMinValue);
+ }
+
+ public void refresh(int newValue) {
+ setValue(newValue, mSeekBar != null);
+ }
+}
diff --git a/device-settings/res/xml/bypass_charging_settings.xml b/device-settings/res/xml/bypass_charging_settings.xml
new file mode 100644
index 0000000..9fbce17
--- /dev/null
+++ b/device-settings/res/xml/bypass_charging_settings.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/device-settings/src/org/lineageos/device/settings/preferences/CustomSeekBarPreference.java b/device-settings/src/org/lineageos/device/settings/preferences/CustomSeekBarPreference.java
new file mode 100644
index 0000000..be63103
--- /dev/null
+++ b/device-settings/src/org/lineageos/device/settings/preferences/CustomSeekBarPreference.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016-2023 crDroid Android 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.
+ */
+package org.lineageos.device.settings.preferences;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.PorterDuff;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.*;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.lineageos.device.settings.R;
+import com.android.settingslib.Utils;
+
+public class CustomSeekBarPreference extends Preference implements SeekBar.OnSeekBarChangeListener {
+ protected final String TAG = getClass().getName();
+ private static final String SETTINGS_NS = "http://schemas.android.com/apk/res/com.android.settings";
+ protected static final String ANDROIDNS = "http://schemas.android.com/apk/res/android";
+
+ protected int mInterval = 1;
+ protected boolean mShowSign = false;
+ protected String mUnits = "";
+ protected boolean mContinuousUpdates = false;
+
+ protected int mMinValue = 0;
+ protected int mMaxValue = 100;
+ protected boolean mDefaultValueExists = false;
+ protected int mDefaultValue;
+ protected boolean mDefaultValueTextExists = false;
+ protected String mDefaultValueText;
+
+ protected int mValue;
+
+ protected TextView mValueTextView;
+ protected ImageView mResetImageView;
+ protected ImageView mMinusImageView;
+ protected ImageView mPlusImageView;
+ protected SeekBar mSeekBar;
+
+ protected boolean mTrackingTouch = false;
+ protected int mTrackingValue;
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomSeekBarPreference);
+ try {
+ mShowSign = a.getBoolean(R.styleable.CustomSeekBarPreference_showSign, mShowSign);
+ String units = a.getString(R.styleable.CustomSeekBarPreference_units);
+ if (units != null)
+ mUnits = " " + units;
+ mContinuousUpdates = a.getBoolean(R.styleable.CustomSeekBarPreference_continuousUpdates, mContinuousUpdates);
+ String defaultValueText = a.getString(R.styleable.CustomSeekBarPreference_defaultValueText);
+ mDefaultValueTextExists = defaultValueText != null && !defaultValueText.isEmpty();
+ if (mDefaultValueTextExists) {
+ mDefaultValueText = defaultValueText;
+ }
+ } finally {
+ a.recycle();
+ }
+
+ try {
+ String newInterval = attrs.getAttributeValue(SETTINGS_NS, "interval");
+ if (newInterval != null)
+ mInterval = Integer.parseInt(newInterval);
+ } catch (Exception e) {
+ Log.e(TAG, "Invalid interval value", e);
+ }
+ mMinValue = attrs.getAttributeIntValue(SETTINGS_NS, "min", mMinValue);
+ mMaxValue = attrs.getAttributeIntValue(ANDROIDNS, "max", mMaxValue);
+ if (mMaxValue < mMinValue)
+ mMaxValue = mMinValue;
+ String defaultValue = attrs.getAttributeValue(ANDROIDNS, "defaultValue");
+ mDefaultValueExists = defaultValue != null && !defaultValue.isEmpty();
+ if (mDefaultValueExists) {
+ mDefaultValue = getLimitedValue(Integer.parseInt(defaultValue));
+ mValue = mDefaultValue;
+ } else {
+ mValue = mMinValue;
+ }
+
+ mSeekBar = new SeekBar(context, attrs);
+ setLayoutResource(R.layout.custom_seekbar_preference);
+ }
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public CustomSeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ androidx.preference.R.attr.seekBarPreferenceStyle,
+ com.android.internal.R.attr.seekBarPreferenceStyle));
+ }
+
+ public CustomSeekBarPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onDependencyChanged(Preference dependency, boolean disableDependent) {
+ super.onDependencyChanged(dependency, disableDependent);
+ this.setShouldDisableView(true);
+ if (mSeekBar != null)
+ mSeekBar.setEnabled(!disableDependent);
+ if (mResetImageView != null)
+ mResetImageView.setEnabled(!disableDependent);
+ if (mPlusImageView != null)
+ mPlusImageView.setEnabled(!disableDependent);
+ if (mMinusImageView != null)
+ mMinusImageView.setEnabled(!disableDependent);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ try
+ {
+ // move our seekbar to the new view we've been given
+ ViewParent oldContainer = mSeekBar.getParent();
+ ViewGroup newContainer = (ViewGroup) holder.findViewById(R.id.seekbar);
+ if (oldContainer != newContainer) {
+ // remove the seekbar from the old view
+ if (oldContainer != null) {
+ ((ViewGroup) oldContainer).removeView(mSeekBar);
+ }
+ // remove the existing seekbar (there may not be one) and add ours
+ newContainer.removeAllViews();
+ newContainer.addView(mSeekBar, ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Error binding view: " + ex.toString());
+ }
+
+ mSeekBar.setMax(getSeekValue(mMaxValue));
+ mSeekBar.setProgress(getSeekValue(mValue));
+ mSeekBar.setEnabled(isEnabled());
+
+ mValueTextView = (TextView) holder.findViewById(R.id.value);
+ mResetImageView = (ImageView) holder.findViewById(R.id.reset);
+ mMinusImageView = (ImageView) holder.findViewById(R.id.minus);
+ mPlusImageView = (ImageView) holder.findViewById(R.id.plus);
+
+ updateValueViews();
+
+ mSeekBar.setOnSeekBarChangeListener(this);
+ mResetImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Toast.makeText(getContext(), getContext().getString(R.string.custom_seekbar_default_value_to_set, getTextValue(mDefaultValue)),
+ Toast.LENGTH_LONG).show();
+ }
+ });
+ mResetImageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ setValue(mDefaultValue, true);
+ return true;
+ }
+ });
+ mMinusImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setValue(mValue - mInterval, true);
+ }
+ });
+ mMinusImageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue < mValue * 2 ? Math.floorDiv(mMaxValue + mMinValue, 2) : mMinValue, true);
+ return true;
+ }
+ });
+ mPlusImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setValue(mValue + mInterval, true);
+ }
+ });
+ mPlusImageView.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View view) {
+ setValue(mMaxValue - mMinValue > mInterval * 2 && mMaxValue + mMinValue > mValue * 2 ? -1 * Math.floorDiv(-1 * (mMaxValue + mMinValue), 2) : mMaxValue, true);
+ return true;
+ }
+ });
+ }
+
+ protected int getLimitedValue(int v) {
+ return v < mMinValue ? mMinValue : (v > mMaxValue ? mMaxValue : v);
+ }
+
+ protected int getSeekValue(int v) {
+ return 0 - Math.floorDiv(mMinValue - v, mInterval);
+ }
+
+ protected String getTextValue(int v) {
+ if (mDefaultValueTextExists && mDefaultValueExists && v == mDefaultValue) {
+ return mDefaultValueText;
+ }
+ return (mShowSign && v > 0 ? "+" : "") + String.valueOf(v) + mUnits;
+ }
+
+ protected void updateValueViews() {
+ if (mValueTextView != null) {
+ if (!mTrackingTouch || mContinuousUpdates) {
+ if (mDefaultValueTextExists && mDefaultValueExists && mValue == mDefaultValue) {
+ mValueTextView.setText(mDefaultValueText + " (" +
+ getContext().getString(R.string.custom_seekbar_default_value) + ")");
+ } else {
+ mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, getTextValue(mValue)) +
+ (mDefaultValueExists && mValue == mDefaultValue ? " (" +
+ getContext().getString(R.string.custom_seekbar_default_value) + ")" : ""));
+ }
+ } else {
+ if (mDefaultValueTextExists && mDefaultValueExists && mTrackingValue == mDefaultValue) {
+ mValueTextView.setText("[" + mDefaultValueText + "]");
+ } else {
+ mValueTextView.setText(getContext().getString(R.string.custom_seekbar_value, "[" + getTextValue(mTrackingValue) + "]"));
+ }
+ }
+ }
+ if (mResetImageView != null) {
+ if (!mDefaultValueExists || mValue == mDefaultValue || mTrackingTouch)
+ mResetImageView.setVisibility(View.INVISIBLE);
+ else
+ mResetImageView.setVisibility(View.VISIBLE);
+ }
+ if (mMinusImageView != null) {
+ if (mValue == mMinValue || mTrackingTouch) {
+ mMinusImageView.setClickable(false);
+ mMinusImageView.setColorFilter(Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorTertiary),
+ PorterDuff.Mode.SRC_IN);
+ } else {
+ mMinusImageView.setClickable(true);
+ mMinusImageView.clearColorFilter();
+ }
+ }
+ if (mPlusImageView != null) {
+ if (mValue == mMaxValue || mTrackingTouch) {
+ mPlusImageView.setClickable(false);
+ mPlusImageView.setColorFilter(Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorTertiary),
+ PorterDuff.Mode.SRC_IN);
+ } else {
+ mPlusImageView.setClickable(true);
+ mPlusImageView.clearColorFilter();
+ }
+ }
+ }
+
+ protected void changeValue(int newValue) {
+ // for subclasses
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ int newValue = getLimitedValue(mMinValue + (progress * mInterval));
+ if (mTrackingTouch && !mContinuousUpdates) {
+ mTrackingValue = newValue;
+ updateValueViews();
+ } else if (mValue != newValue) {
+ // change rejected, revert to the previous value
+ if (!callChangeListener(newValue)) {
+ mSeekBar.setProgress(getSeekValue(mValue));
+ return;
+ }
+ // change accepted, store it
+ changeValue(newValue);
+ persistInt(newValue);
+
+ mValue = newValue;
+ updateValueViews();
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mTrackingValue = mValue;
+ mTrackingTouch = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mTrackingTouch = false;
+ if (!mContinuousUpdates)
+ onProgressChanged(mSeekBar, getSeekValue(mTrackingValue), false);
+ notifyChanged();
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ if (restoreValue)
+ mValue = getPersistedInt(mValue);
+ }
+
+ @Override
+ public void setDefaultValue(Object defaultValue) {
+ if (defaultValue instanceof Integer)
+ setDefaultValue((Integer) defaultValue, mSeekBar != null);
+ else
+ setDefaultValue(defaultValue == null ? (String) null : defaultValue.toString(), mSeekBar != null);
+ }
+
+ public void setDefaultValue(int newValue, boolean update) {
+ newValue = getLimitedValue(newValue);
+ if (!mDefaultValueExists || mDefaultValue != newValue) {
+ mDefaultValueExists = true;
+ mDefaultValue = newValue;
+ if (update)
+ updateValueViews();
+ }
+ }
+
+ public void setDefaultValue(String newValue, boolean update) {
+ if (mDefaultValueExists && (newValue == null || newValue.isEmpty())) {
+ mDefaultValueExists = false;
+ if (update)
+ updateValueViews();
+ } else if (newValue != null && !newValue.isEmpty()) {
+ setDefaultValue(Integer.parseInt(newValue), update);
+ }
+ }
+
+ public void setValue(int newValue) {
+ mValue = getLimitedValue(newValue);
+ if (mSeekBar != null) mSeekBar.setProgress(getSeekValue(mValue));
+ }
+
+ public void setValue(int newValue, boolean update) {
+ newValue = getLimitedValue(newValue);
+ if (mValue != newValue) {
+ if (update)
+ mSeekBar.setProgress(getSeekValue(newValue));
+ else
+ mValue = newValue;
+ }
+ }
+
+ public int getValue() {
+ return mValue;
+ }
+
+ public void setMax(int max) {
+ mMaxValue = max < mMinValue ? mMinValue : max;
+ mSeekBar.setMax(mMaxValue);
+ }
+
+ public void setMin(int min) {
+ mMinValue = min > mMaxValue ? mMaxValue : min;
+ mSeekBar.setMin(mMinValue);
+ }
+
+ public void refresh(int newValue) {
+ setValue(newValue, mSeekBar != null);
+ }
+}
diff --git a/device.mk b/device.mk
new file mode 100644
index 0000000..6a09733
--- /dev/null
+++ b/device.mk
@@ -0,0 +1,70 @@
+#
+# Copyright (C) 2021-2025 The LineageOS Project
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+# AAPT
+PRODUCT_AAPT_CONFIG := normal
+PRODUCT_AAPT_PREF_CONFIG := xxxhdpi
+
+# Audio
+PRODUCT_COPY_FILES += \
+ $(LOCAL_PATH)/configs/audio/audio_policy_volumes.xml:$(TARGET_COPY_OUT_VENDOR)/etc/audio_policy_volumes.xml \
+ $(LOCAL_PATH)/configs/audio/default_volume_tables.xml:$(TARGET_COPY_OUT_VENDOR)/etc/default_volume_tables.xml
+
+# Boot animation
+TARGET_SCREEN_HEIGHT := 3168
+TARGET_SCREEN_WIDTH := 1440
+
+# Display
+PRODUCT_COPY_FILES += \
+ $(LOCAL_PATH)/configs/display/displayconfig.xml:$(TARGET_COPY_OUT_VENDOR)/etc/displayconfig/display_id_4630946756802996883.xml
+
+# LiveDisplay
+$(call soong_config_set,OPLUS_LINEAGE_LIVEDISPLAY_HAL,ENABLE_AF,false)
+$(call soong_config_set,OPLUS_LINEAGE_LIVEDISPLAY_HAL,ENABLE_SE,false)
+
+
+# Overlays
+DEVICE_PACKAGE_OVERLAYS += \
+ $(LOCAL_PATH)/overlay-lineage
+
+PRODUCT_PACKAGES += \
+ OPlusFrameworksResTarget \
+ OPlusSettingsProviderResTarget \
+ OPlusSettingsResTarget \
+ OPlusSystemUIResTarget
+
+# Power
+$(call soong_config_set,qtipower,mode_ext_lib,power-ext-oplus)
+
+# PowerShare
+PRODUCT_PACKAGES += \
+ vendor.lineage.powershare-service.oplus
+
+# Regional properties
+PRODUCT_COPY_FILES += \
+ $(LOCAL_PATH)/recovery/root/vendor/odm/etc/23821/build.default.prop:$(TARGET_COPY_OUT_ODM)/etc/23821/build.default.prop \
+ $(LOCAL_PATH)/recovery/root/vendor/odm/etc/23893/build.EU.prop:$(TARGET_COPY_OUT_ODM)/etc/23893/build.EU.prop \
+ $(LOCAL_PATH)/recovery/root/vendor/odm/etc/23893/build.IN.prop:$(TARGET_COPY_OUT_ODM)/etc/23893/build.IN.prop \
+ $(LOCAL_PATH)/recovery/root/vendor/odm/etc/23893/build.NA.prop:$(TARGET_COPY_OUT_ODM)/etc/23893/build.NA.prop \
+ $(LOCAL_PATH)/recovery/root/vendor/odm/etc/23893/build.default.prop:$(TARGET_COPY_OUT_ODM)/etc/23893/build.default.prop
+
+# Soong namespaces
+PRODUCT_SOONG_NAMESPACES += \
+ $(LOCAL_PATH)
+
+# Touch features
+$(call soong_config_set,OPLUS_LINEAGE_TOUCH_HAL,ENABLE_GM,true)
+$(call soong_config_set,OPLUS_LINEAGE_TOUCH_HAL,ENABLE_HTPR,false)
+
+# Vibrator
+$(call soong_config_set,OPLUS_LINEAGE_VIBRATOR_HAL,USE_EFFECT_STREAM,true)
+$(call soong_config_set,OPLUS_LINEAGE_VIBRATOR_HAL,INCLUDE_DIR,$(LOCAL_PATH)/vibrator/include)
+
+# Inherit from the common OEM chipset makefile.
+$(call inherit-product, device/oneplus/sm8750-common/common.mk)
+
+# Inherit from the proprietary files makefile.
+$(call inherit-product, vendor/oneplus/dodge/dodge-vendor.mk)
diff --git a/overlay-lineage/frameworks/base/core/res/res/values/config.xml b/overlay-lineage/frameworks/base/core/res/res/values/config.xml
index 2e28aea..35322f9 100644
--- a/overlay-lineage/frameworks/base/core/res/res/values/config.xml
+++ b/overlay-lineage/frameworks/base/core/res/res/values/config.xml
@@ -16,4 +16,5 @@
true
true
+
diff --git a/sepolicy/vendor/system_app.te b/sepolicy/vendor/system_app.te
new file mode 100644
index 0000000..c47019d
--- /dev/null
+++ b/sepolicy/vendor/system_app.te
@@ -0,0 +1,8 @@
+# Bypass Charging
+allow system_app vendor_sysfs_usb_supply:file w_file_perms;
+
+# Gamebar
+allow system_app { proc_stat vendor_sysfs_kgsl vendor_sysfs_kgsl_gpuclk }:file r_file_perms;
+
+# OnePulse PWM
+rw_dir_file(system_app, vendor_sysfs_graphics);