DeviceSettings: rework bypass charging

* Drop Lineage Health dependency
* Turn bypass charging into a tri-state (0-BYPASS_OFF, 1-BYPASS_WAITING, 2-BYPASS_OFF
* Add target limit

Change-Id: I06b6e9e8da7ffc20c769d47627d07cc189e884b4
This commit is contained in:
elpaablo
2025-10-11 18:34:29 +01:00
committed by osm1019
parent 8949136942
commit 1635d42a57
24 changed files with 1649 additions and 188 deletions

36
BoardConfig.mk Normal file
View File

@@ -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

View File

@@ -111,6 +111,12 @@
android:resource="@string/bypass_charging_summary" /> android:resource="@string/bypass_charging_summary" />
</activity> </activity>
<!-- BypassCharging service to monitor battery level -->
<service
android:name=".bypasschrg.BypassChargingService"
android:exported="true"
android:foregroundServiceType="connectedDevice" />
<!-- BypassChargingTile service --> <!-- BypassChargingTile service -->
<service <service
android:name=".bypasschrg.BypassChargingTile" android:name=".bypasschrg.BypassChargingTile"

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="30dp"
android:viewportWidth="32"
android:viewportHeight="40"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M23,18a6,6 0,1 0,-6 -6A6,6 0,0 0,23 18ZM22,10a1,1 0,0 1,2 0v1.38l1.45,0.73a1,1 0,0 1,0.44 1.34A1,1 0,0 1,25 14a0.93,0.93 0,0 1,-0.45 -0.11l-2,-1A1,1 0,0 1,22 12Z" />
<path
android:fillColor="@android:color/white"
android:pathData="M2,17v4a1,1 0,0 0,1 1H4V16H3A1,1 0,0 0,2 17Z" />
<path
android:fillColor="@android:color/white"
android:pathData="M23,20a8,8 0,0 1,-8 -8H8a2,2 0,0 0,-2 2V24a2,2 0,0 0,2 2H28a2,2 0,0 0,2 -2V15.86A8,8 0,0 1,23 20Z" />
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018-2021 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0" >
<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="M24 4C12.972 4 4 12.972 4 24s8.972 20 20 20s20 -8.972 20 -20S35.028 4 24 4zM32.5 25.5h-17c-0.829 0 -1.5 -0.671 -1.5 -1.5s0.671 -1.5 1.5 -1.5h17c0.829 0 1.5 0.671 1.5 1.5S33.329 25.5 32.5 25.5z" />
</vector>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2018-2021 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0" >
<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="M24 4C12.972 4 4 12.972 4 24s8.972 20 20 20s20 -8.972 20 -20S35.028 4 24 4zM32.5 25.5h-7v7c0 0.829 -0.672 1.5 -1.5 1.5s-1.5 -0.671 -1.5 -1.5v-7h-7c-0.828 0 -1.5 -0.671 -1.5 -1.5s0.672 -1.5 1.5 -1.5h7v-7c0 -0.829 0.672 -1.5 1.5 -1.5s1.5 0.671 1.5 1.5v7h7c0.828 0 1.5 0.671 1.5 1.5S33.328 25.5 32.5 25.5z" />
</vector>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2019 Havoc-OS
Copyright (C) 2022 DerpFest
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0" >
<path
android:fillColor="?android:attr/colorControlNormal"
android:pathData="M14.123456746339798,12.042328000068665 c0,-0.7430335164070133 -0.6079365134239197,-1.3509700298309326 -1.3509700298309326,-1.3509700298309326 s-1.3509700298309326,0.6079365134239197 -1.3509700298309326,1.3509700298309326 s0.6079365134239197,1.3509700298309326 1.3509700298309326,1.3509700298309326 s1.3509700298309326,-0.6079365134239197 1.3509700298309326,-1.3509700298309326 zM12.772486716508865,5.962962865829468 c-3.3571605241298674,0 -6.079365134239197,2.7222046101093293 -6.079365134239197,6.079365134239197 L4.66666653752327,12.042328000068665 l2.7019400596618652,2.7019400596618652 l2.7019400596618652,-2.7019400596618652 L8.044091612100601,12.042328000068665 c0,-2.6141270077228547 2.1142680966854095,-4.728395104408264 4.728395104408264,-4.728395104408264 s4.728395104408264,2.1142680966854095 4.728395104408264,4.728395104408264 s-2.1142680966854095,4.728395104408264 -4.728395104408264,4.728395104408264 c-1.0199823725223542,0 -1.965661393404007,-0.3309876573085789 -2.742469160556793,-0.8781305193901066 l-0.9591887211799618,0.9726984214782719 C10.097566057443618,17.648853623867033 11.380987585783004,18.12169313430786 12.772486716508865,18.12169313430786 c3.3571605241298674,0 6.079365134239197,-2.7222046101093293 6.079365134239197,-6.079365134239197 s-2.7222046101093293,-6.079365134239197 -6.079365134239197,-6.079365134239197 z" />
</vector>

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="50"
android:viewportHeight="50"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M41.9,23.9c-0.3,-6.1 -4,-11.8 -9.5,-14.4 -6,-2.7 -13.3,-1.6 -18.3,2.6 -4.8,4 -7,10.5 -5.6,16.6 1.3,6 6,10.9 11.9,12.5 7.1,2 13.6,-1.4 17.6,-7.2 -3.6,4.8 -9.1,8 -15.2,6.9 -6.1,-1.1 -11.1,-5.7 -12.5,-11.7 -1.5,-6.4 1.5,-13.1 7.2,-16.4 5.9,-3.4 14.2,-2.1 18.1,3.7 1,1.4 1.7,3.1 2,4.8 0.3,1.4 0.2,2.9 0.4,4.3 0.2,1.3 1.3,3 2.8,2.1 1.3,-0.8 1.2,-2.5 1.1,-3.8 0,-0.4 0.1,0.7 0,0z" />
</vector>

View File

@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2017-2022 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/activatedBackgroundIndicator"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="48dp"
android:gravity="start|center_vertical"
android:orientation="horizontal"
android:paddingLeft="0dp"
android:paddingStart="0dp"
android:paddingRight="8dp"
android:paddingEnd="7dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<androidx.preference.internal.PreferenceImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:paddingStart="8dp"
app:maxWidth="48dp"
app:maxHeight="48dp"/>
</LinearLayout>
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee" />
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
android:layout_alignStart="@android:id/title"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="10"
android:ellipsize="end" />
<RelativeLayout
android:id="@+id/value_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/summary"
android:layout_alignStart="@android:id/title" >
<TextView
android:id="@+id/value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="1"
android:ellipsize="end" />
<ImageView
android:id="@+id/reset"
android:src="@drawable/ic_custom_seekbar_reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_toEndOf="@id/value"
android:layout_centerVertical="true" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/seekbar_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/value_frame"
android:layout_alignStart="@android:id/title" >
<ImageView
android:id="@+id/minus"
android:src="@drawable/ic_custom_seekbar_minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<ImageView
android:id="@+id/plus"
android:src="@drawable/ic_custom_seekbar_plus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true" />
<LinearLayout
android:id="@+id/seekbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_toEndOf="@id/minus"
android:layout_toStartOf="@id/plus"
android:layout_centerVertical="true" />
</RelativeLayout>
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="end|center_vertical"
android:paddingStart="16dp"
android:orientation="vertical" />
</LinearLayout>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomSeekBarPreference">
<attr name="defaultValueText" format="string" />
<attr name="interval" format="integer" />
<attr name="showSign" format="boolean" />
<attr name="units" format="string|reference" />
<attr name="continuousUpdates" format="boolean" />
</declare-styleable>
</resources>

View File

@@ -81,7 +81,20 @@
<string name="bypass_charging_unavailable">Bypass charging is not supported on this device</string> <string name="bypass_charging_unavailable">Bypass charging is not supported on this device</string>
<string name="bypass_charging_enabled">Enabled</string> <string name="bypass_charging_enabled">Enabled</string>
<string name="bypass_charging_disabled">Disabled</string> <string name="bypass_charging_disabled">Disabled</string>
<<<<<<< HEAD:DeviceSettings/res/values/strings.xml
<string name="bypass_charging_error">Coudn\'t enable Bypass Charging.</string> <string name="bypass_charging_error">Coudn\'t enable Bypass Charging.</string>
=======
<string name="bypass_charging_error">It was not possible to change Bypass Charging status. Please, try again later.</string>
<string name="bypass_charging_target_title">Target level</string>
<string name="bypass_charging_target_summary">Battery will charge until it reaches target level, then bypass charging will be activated.</string>
<string name="bypass_charging_waiting">Battery will charge until it reaches target level</string>
<!-- Custom seekbar -->
<string name="custom_seekbar_value">Value: <xliff:g id="v">%s</xliff:g></string>
<string name="custom_seekbar_default_value">by default</string>
<string name="custom_seekbar_default_value_to_set">Default value: <xliff:g id="v">%s</xliff:g>\nLong tap to set</string>
<string name="custom_seekbar_default_value_is_set">Default value is set</string>
>>>>>>> 96046be (DeviceSettings: rework bypass charging):device-settings/res/values/strings.xml
<!-- GameBar Overlay --> <!-- GameBar Overlay -->
<string name="game_bar_title">GameBar</string> <string name="game_bar_title">GameBar</string>

View File

@@ -1,14 +1,35 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<SwitchPreferenceCompat xmlns:settings="http://schemas.android.com/apk/res-auto">
android:key="bypass_charging"
android:title="@string/bypass_charging_title"
android:icon="@drawable/ic_bypass_charging"
android:summary="@string/bypass_charging_summary"
android:persistent="false" />
<com.android.settingslib.widget.FooterPreference <PreferenceCategory
android:key="bypass_charging_info" android:key="bypass_charging_category"
android:title="@string/bypass_charging_info_title" android:title="@string/bypass_charging_category_title">
android:selectable="false" />
<SwitchPreferenceCompat
android:key="bypass_charging"
android:title="@string/bypass_charging_title"
android:icon="@drawable/ic_bypass_charging"
android:summary="@string/bypass_charging_summary"
android:persistent="false" />
<org.lineageos.device.DeviceSettings.preferences.CustomSeekBarPreference
android:key="bypass_charging_target"
android:title="@string/bypass_charging_target_title"
android:summary="@string/bypass_charging_target_summary"
android:icon="@drawable/ic_bypass_waiting"
android:dependency="bypass_charging"
android:defaultValue="0"
android:max="100"
settings:min="0"
settings:interval="5"
settings:units="%"
settings:showSign="true" />
<com.android.settingslib.widget.FooterPreference
android:key="bypass_charging_info"
android:title="@string/bypass_charging_info_title"
android:selectable="false" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -29,14 +29,14 @@ import android.media.AudioManager;
public class Constants { public class Constants {
public static final String SLIDER_STATE /* Alert Slider */
= "/proc/tristatekey/tri_state"; 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 KEY_NOTIF_SLIDER_PANEL = "notification_slider";
public static final String NOTIF_SLIDER_USAGE_KEY = "slider_usage"; public static final String KEY_NOTIF_SLIDER_USAGE = "slider_usage";
public static final String NOTIF_SLIDER_ACTION_TOP_KEY = "action_top_position"; public static final String KEY_NOTIF_SLIDER_ACTION_TOP = "action_top_position";
public static final String NOTIF_SLIDER_ACTION_MIDDLE_KEY = "action_middle_position"; public static final String KEY_NOTIF_SLIDER_ACTION_MIDDLE = "action_middle_position";
public static final String NOTIF_SLIDER_ACTION_BOTTOM_KEY = "action_bottom_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_USAGE = "usage";
public static final String EXTRA_SLIDER_ACTIONS = "actions"; public static final String EXTRA_SLIDER_ACTIONS = "actions";
@@ -76,4 +76,17 @@ public class Constants {
// Holds <preference_key> -> <proc_node> mapping // Holds <preference_key> -> <proc_node> mapping
public static final Map<String, String> sBooleanNodePreferenceMap = new HashMap<>(); public static final Map<String, String> sBooleanNodePreferenceMap = new HashMap<>();
public static final Map<String, String> sStringNodePreferenceMap = new HashMap<>(); public static final Map<String, String> 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;
} }

View File

@@ -98,17 +98,30 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
// mUSB2FastChargeModeSwitch.setEnabled(false); // 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(); initNotificationSliderPreference();
} }
private void initNotificationSliderPreference() { private void initNotificationSliderPreference() {
registerPreferenceListener(Constants.NOTIF_SLIDER_USAGE_KEY); registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_USAGE);
registerPreferenceListener(Constants.NOTIF_SLIDER_ACTION_TOP_KEY); registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_ACTION_TOP);
registerPreferenceListener(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY); registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE);
registerPreferenceListener(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY); registerPreferenceListener(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM);
ListPreference usagePref = (ListPreference) findPreference( ListPreference usagePref = (ListPreference) findPreference(
Constants.NOTIF_SLIDER_USAGE_KEY); Constants.KEY_NOTIF_SLIDER_USAGE);
handleSliderUsageChange(usagePref.getValue()); handleSliderUsageChange(usagePref.getValue());
} }
@@ -138,18 +151,28 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
// Utils.writeValue(FILE_FAST_CHARGE, enabled ? "1" : "0"); // Utils.writeValue(FILE_FAST_CHARGE, enabled ? "1" : "0");
// return true; // 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(); String key = preference.getKey();
switch (key) { switch (key) {
case Constants.NOTIF_SLIDER_USAGE_KEY: case Constants.KEY_NOTIF_SLIDER_USAGE:
return handleSliderUsageChange((String) newValue) && return handleSliderUsageChange((String) newValue) &&
handleSliderUsageDefaultsChange((String) newValue) && handleSliderUsageDefaultsChange((String) newValue) &&
notifySliderUsageChange((String) newValue); notifySliderUsageChange((String) newValue);
case Constants.NOTIF_SLIDER_ACTION_TOP_KEY: case Constants.KEY_NOTIF_SLIDER_ACTION_TOP:
return notifySliderActionChange(0, (String) newValue); return notifySliderActionChange(0, (String) newValue);
case Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY: case Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE:
return notifySliderActionChange(1, (String) newValue); return notifySliderActionChange(1, (String) newValue);
case Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY: case Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM:
return notifySliderActionChange(2, (String) newValue); return notifySliderActionChange(2, (String) newValue);
default: default:
break; break;
@@ -252,11 +275,11 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
private boolean updateSliderActions(int entriesResId, int entryValuesResId) { private boolean updateSliderActions(int entriesResId, int entryValuesResId) {
String[] entries = getResources().getStringArray(entriesResId); String[] entries = getResources().getStringArray(entriesResId);
String[] entryValues = getResources().getStringArray(entryValuesResId); String[] entryValues = getResources().getStringArray(entryValuesResId);
return updateSliderPreference(Constants.NOTIF_SLIDER_ACTION_TOP_KEY, return updateSliderPreference(Constants.KEY_NOTIF_SLIDER_ACTION_TOP,
entries, entryValues) && entries, entryValues) &&
updateSliderPreference(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY, updateSliderPreference(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE,
entries, entryValues) && entries, entryValues) &&
updateSliderPreference(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY, updateSliderPreference(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM,
entries, entryValues); entries, entryValues);
} }
@@ -266,11 +289,11 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
return false; return false;
} }
return updateSliderPreferenceValue(Constants.NOTIF_SLIDER_ACTION_TOP_KEY, return updateSliderPreferenceValue(Constants.KEY_NOTIF_SLIDER_ACTION_TOP,
defaults[0]) && defaults[0]) &&
updateSliderPreferenceValue(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY, updateSliderPreferenceValue(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE,
defaults[1]) && defaults[1]) &&
updateSliderPreferenceValue(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY, updateSliderPreferenceValue(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM,
defaults[2]); defaults[2]);
} }
@@ -300,15 +323,15 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
ListPreference p; ListPreference p;
p = (ListPreference) findPreference( p = (ListPreference) findPreference(
Constants.NOTIF_SLIDER_ACTION_TOP_KEY); Constants.KEY_NOTIF_SLIDER_ACTION_TOP);
actions[0] = Integer.parseInt(p.getValue()); actions[0] = Integer.parseInt(p.getValue());
p = (ListPreference) findPreference( p = (ListPreference) findPreference(
Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY); Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE);
actions[1] = Integer.parseInt(p.getValue()); actions[1] = Integer.parseInt(p.getValue());
p = (ListPreference) findPreference( p = (ListPreference) findPreference(
Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY); Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM);
actions[2] = Integer.parseInt(p.getValue()); actions[2] = Integer.parseInt(p.getValue());
return actions; return actions;
@@ -322,7 +345,7 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
private boolean notifySliderActionChange(int index, String value) { private boolean notifySliderActionChange(int index, String value) {
ListPreference p = (ListPreference) findPreference( ListPreference p = (ListPreference) findPreference(
Constants.NOTIF_SLIDER_USAGE_KEY); Constants.KEY_NOTIF_SLIDER_USAGE);
int usage = Integer.parseInt(p.getValue()); int usage = Integer.parseInt(p.getValue());
int[] actions = getCurrentSliderActions(); int[] actions = getCurrentSliderActions();
@@ -348,7 +371,7 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
SharedPreferences prefs = context.getSharedPreferences( SharedPreferences prefs = context.getSharedPreferences(
context.getPackageName() + "_preferences", Context.MODE_PRIVATE); 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)); res.getString(R.string.config_defaultNotificationSliderUsage));
int defaultsResId = getDefaultResIdForUsage(usage); int defaultsResId = getDefaultResIdForUsage(usage);
@@ -362,19 +385,19 @@ public class DeviceSettings extends SettingsBasePreferenceFragment
} }
String actionTop = prefs.getString( String actionTop = prefs.getString(
Constants.NOTIF_SLIDER_ACTION_TOP_KEY, defaults[0]); Constants.KEY_NOTIF_SLIDER_ACTION_TOP, defaults[0]);
String actionMiddle = prefs.getString( String actionMiddle = prefs.getString(
Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY, defaults[1]); Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE, defaults[1]);
String actionBottom = prefs.getString( String actionBottom = prefs.getString(
Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY, defaults[2]); Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM, defaults[2]);
prefs.edit() prefs.edit()
.putString(Constants.NOTIF_SLIDER_USAGE_KEY, usage) .putString(Constants.KEY_NOTIF_SLIDER_USAGE, usage)
.putString(Constants.NOTIF_SLIDER_ACTION_TOP_KEY, actionTop) .putString(Constants.KEY_NOTIF_SLIDER_ACTION_TOP, actionTop)
.putString(Constants.NOTIF_SLIDER_ACTION_MIDDLE_KEY, actionMiddle) .putString(Constants.KEY_NOTIF_SLIDER_ACTION_MIDDLE, actionMiddle)
.putString(Constants.NOTIF_SLIDER_ACTION_BOTTOM_KEY, actionBottom) .putString(Constants.KEY_NOTIF_SLIDER_ACTION_BOTTOM, actionBottom)
.commit(); .commit();
sendUpdateBroadcast(context, Integer.parseInt(usage), new int[] { 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) { private static int getDefaultResIdForUsage(String usage) {
switch (usage) { switch (usage) {
case Constants.NOTIF_SLIDER_FOR_NOTIFICATION: case Constants.NOTIF_SLIDER_FOR_NOTIFICATION:

View File

@@ -80,7 +80,7 @@ public abstract class SliderControllerBase {
} }
try { 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]); ret = processAction(mActions[state - 1]);
if (ret > 0 && notify) { if (ret > 0 && notify) {
sendUpdateBroadcast(context, state - 1, ret); sendUpdateBroadcast(context, state - 1, ret);

View File

@@ -17,53 +17,30 @@
package org.lineageos.device.DeviceSettings.bypasschrg; 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.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.os.BatteryManager;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
import android.util.Log; import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.lineageos.device.DeviceSettings.Constants;
import org.lineageos.device.DeviceSettings.R; import org.lineageos.device.DeviceSettings.R;
import org.lineageos.device.DeviceSettings.utils.FileUtils; 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 { 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 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_ENABLED = "0";
private static final String BYPASS_CHARGING_DISABLED = "1"; 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 int mBatteryLevel;
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 Context mContext; private Context mContext;
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
private final Object mLock = new Object();
private static BypassChargingController sInstance; private static BypassChargingController sInstance;
public static synchronized BypassChargingController getInstance(Context context) { public static synchronized BypassChargingController getInstance(Context context) {
@@ -76,37 +53,45 @@ public class BypassChargingController {
private BypassChargingController(Context context) { private BypassChargingController(Context context) {
mContext = context.getApplicationContext(); mContext = context.getApplicationContext();
mContentResolver = mContext.getContentResolver(); mContentResolver = mContext.getContentResolver();
mBatteryLevel = getLevelFromIntent();
if (isValidLevel(mBatteryLevel)) {
saveCurrentBatteryLevel(mBatteryLevel);
}
} }
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) { // Called from Service when battery level changes
@Override public void onBatteryLevelChanged(int level) {
public void onChange(boolean selfChange, Uri uri) { synchronized (mLock) {
switch(uri.getLastPathSegment()) { if (isValidLevel(level) && (mBatteryLevel == -1 || mBatteryLevel != level)) {
case KEY_CHARGING_CONTROL_ENABLED: mBatteryLevel = level;
case KEY_CHARGING_CONTROL_MODE: saveCurrentBatteryLevel(level);
case KEY_CHARGING_CONTROL_LIMIT: maybeEnableBypassCharging();
break; if (DEBUG) Log.d(TAG, "Battery level changed (service): " + level + "%");
} }
} }
};
public boolean isBypassChargingSupported() {
return isNodeAccessible(BYPASS_CHARGING_NODE);
} }
public boolean isBypassChargingEnabled() { // get battery level using a sticky intent
try { private int getLevelFromIntent() {
String value = FileUtils.readOneLine(BYPASS_CHARGING_NODE); android.content.IntentFilter filter =
return value != null && BYPASS_CHARGING_ENABLED.equals(value); new android.content.IntentFilter(android.content.Intent.ACTION_BATTERY_CHANGED);
} catch (Exception e) { android.content.Intent intent = mContext.registerReceiver(null, filter);
Log.e(TAG, "Failed to read bypass sysnode", e);
return false; 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) { private boolean isNodeAccessible(String node) {
try { try {
String value = FileUtils.readOneLine(node); String status = FileUtils.readOneLine(node);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Node " + node + " not accessible", e); Log.e(TAG, "Node " + node + " not accessible", e);
@@ -114,102 +99,146 @@ public class BypassChargingController {
} }
} }
private boolean writeToNode(String value) { private boolean writeToNode(String status) {
try { synchronized (mLock) {
FileUtils.writeLine(BYPASS_CHARGING_NODE, value); try {
} catch (Exception e) { FileUtils.writeLine(Constants.NODE_BYPASS_CHARGING, status);
Log.e(TAG, "Failed to write bypass sysnode", e); } 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 false;
} }
return true;
}
public void setBypassCharging(boolean enable) { if (getBypassChargingTarget() > level) {
if (enable) { saveBypassChargingStatus(Constants.BYPASS_WAITING);
enableBypassCharging(); BypassChargingService.start(mContext);
return true;
} }
else { else if (writeToNode(BYPASS_CHARGING_ENABLED)) {
disableBypassCharging(); saveBypassChargingStatus(Constants.BYPASS_ON);
BypassChargingService.stop(mContext);
return true;
} }
return false;
} }
private void enableBypassCharging() { public boolean disableBypassCharging() {
setChargingControlEnabled(true); if (writeToNode(BYPASS_CHARGING_DISABLED)) {
setChargingControlMode(MODE_LIMIT); saveBypassChargingStatus(Constants.BYPASS_OFF);
setChargingControlLimit(CC_LIMIT_MIN); BypassChargingService.stop(mContext);
writeToNode(BYPASS_CHARGING_ENABLED); return true;
}
return false;
} }
public void disableBypassCharging() { private void saveBypassChargingStatus(int status) {
writeToNode(BYPASS_CHARGING_DISABLED);
setChargingControlLimit(CC_LIMIT_DEF);
// setChargingControlMode(MODE_AUTO);
setChargingControlEnabled(false);
}
private void saveBypassChargingEnabled(boolean enabled) {
PreferenceManager.getDefaultSharedPreferences(mContext) PreferenceManager.getDefaultSharedPreferences(mContext)
.edit() .edit()
.putBoolean(KEY_BYPASS_CHARGING_ENABLED, enabled) .putInt(Constants.KEY_BYPASS_CHARGING, status)
.commit(); .apply();
} }
private boolean isSavedBypassChargingEnabled() { public int getBypassChargingStatus() {
return PreferenceManager.getDefaultSharedPreferences(mContext) 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) PreferenceManager.getDefaultSharedPreferences(mContext)
.edit() .edit()
.putInt(KEY_CHARGING_CONTROL_MODE, getChargingControlMode()) .putInt(Constants.KEY_BYPASS_CHARGING_TARGET, target)
.putInt(KEY_CHARGING_CONTROL_LIMIT, getChargingControlLimit()) .apply();
.putBoolean(KEY_CHARGING_CONTROL_ENABLED, isChargingControlEnabled())
.commit();
} }
private void restoreChargingControlSettings() { public int getBypassChargingTarget() {
SharedPreferences sharedPreferences = return PreferenceManager.getDefaultSharedPreferences(mContext)
PreferenceManager.getDefaultSharedPreferences(mContext); .getInt(Constants.KEY_BYPASS_CHARGING_TARGET, 0);
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));
} }
private boolean isChargingControlEnabled() { private void saveCurrentBatteryLevel(int level) {
return Settings.System.getInt(mContentResolver, synchronized (mLock) {
KEY_CHARGING_CONTROL_ENABLED, 0) != 0; if (isValidLevel(level)) {
} PreferenceManager.getDefaultSharedPreferences(mContext)
.edit()
private void setChargingControlEnabled(boolean enabled) { .putInt(KEY_BATTERY_LEVEL, level)
Settings.System.putInt(mContentResolver, .apply();
KEY_CHARGING_CONTROL_ENABLED, enabled ? 1 : 0); } else {
} if (DEBUG) Log.w(TAG, "Attempted to save invalid battery level: " + level);
}
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;
} }
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) { private void showToast(int resId) {

View File

@@ -21,12 +21,12 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.TwoStatePreference; import androidx.preference.TwoStatePreference;
import org.lineageos.device.DeviceSettings.Constants;
import org.lineageos.device.DeviceSettings.R; import org.lineageos.device.DeviceSettings.R;
import org.lineageos.device.DeviceSettings.preferences.CustomSeekBarPreference;
public class BypassChargingFragment extends PreferenceFragmentCompat { public class BypassChargingFragment extends PreferenceFragmentCompat {
private static final String KEY_BYPASS_CHARGING = "bypass_charging";
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.bypass_charging_settings, rootKey); setPreferencesFromResource(R.xml.bypass_charging_settings, rootKey);
@@ -35,16 +35,33 @@ public class BypassChargingFragment extends PreferenceFragmentCompat {
BypassChargingController.getInstance(getContext()); BypassChargingController.getInstance(getContext());
boolean bypassSupported = bypassController.isBypassChargingSupported(); boolean bypassSupported = bypassController.isBypassChargingSupported();
TwoStatePreference bypassPreference = findPreference(KEY_BYPASS_CHARGING); TwoStatePreference bypassPreference = findPreference(Constants.KEY_BYPASS_CHARGING);
bypassPreference.setEnabled(bypassSupported); if (bypassPreference != null) {
if (bypassSupported) { bypassPreference.setEnabled(bypassSupported);
bypassPreference.setChecked(bypassController.isBypassChargingEnabled()); if (bypassSupported) {
bypassPreference.setOnPreferenceChangeListener((pref, newValue) -> { bypassPreference.setChecked(bypassController.getBypassChargingStatus() != Constants.BYPASS_OFF);
bypassController.setBypassCharging((boolean) newValue); bypassPreference.setOnPreferenceChangeListener((pref, newValue) -> {
return true; boolean enable = (boolean) newValue;
}); if (enable) {
} else { bypassController.enableBypassCharging();
bypassPreference.setSummary(R.string.bypass_charging_unavailable); }
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;
});
} }
} }
} }

View File

@@ -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);
}
}

View File

@@ -16,9 +16,12 @@
package org.lineageos.device.DeviceSettings.bypasschrg; package org.lineageos.device.DeviceSettings.bypasschrg;
import android.graphics.drawable.Icon;
import android.service.quicksettings.Tile; import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService; import android.service.quicksettings.TileService;
import org.lineageos.device.DeviceSettings.Constants;
import org.lineageos.device.DeviceSettings.R; import org.lineageos.device.DeviceSettings.R;
public class BypassChargingTile extends TileService { public class BypassChargingTile extends TileService {
@@ -35,26 +38,42 @@ public class BypassChargingTile extends TileService {
@Override @Override
public void onStartListening() { public void onStartListening() {
mEnabled = mBypassController.isBypassChargingEnabled(); int status = mBypassController.getBypassChargingStatus();
updateTileState(); mEnabled = status != Constants.BYPASS_OFF;
updateTileState(status);
} }
@Override @Override
public void onClick() { public void onClick() {
if (mEnabled == mBypassController.isBypassChargingEnabled()) { boolean enabled = mBypassController.getBypassChargingStatus() != Constants.BYPASS_OFF;
mEnabled = !mEnabled; if (mEnabled == enabled) {
updateTileState(); boolean success;
mBypassController.setBypassCharging(mEnabled); 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(); Tile tile = getQsTile();
if (tile == null) return; 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.setLabel(getString(R.string.bypass_charging_title));
tile.setContentDescription(getString(R.string.bypass_charging_summary)); 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(); tile.updateTile();
} }
} }

View File

@@ -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);
}
}

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
android:key="bypass_charging_category"
android:title="@string/bypass_charging_category_title">
<SwitchPreferenceCompat
android:key="bypass_charging"
android:title="@string/bypass_charging_title"
android:icon="@drawable/ic_bypass_charging"
android:summary="@string/bypass_charging_summary"
android:persistent="false" />
<org.lineageos.device.settings.preferences.CustomSeekBarPreference
android:key="bypass_charging_target"
android:title="@string/bypass_charging_target_title"
android:summary="@string/bypass_charging_target_summary"
android:icon="@drawable/ic_bypass_waiting"
android:dependency="bypass_charging"
android:defaultValue="0"
android:max="100"
settings:min="0"
settings:interval="5"
settings:units="%"
settings:showSign="true" />
<com.android.settingslib.widget.FooterPreference
android:key="bypass_charging_info"
android:title="@string/bypass_charging_info_title"
android:selectable="false" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -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);
}
}

70
device.mk Normal file
View File

@@ -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)

View File

@@ -16,4 +16,5 @@
<bool name="config_proximityCheckOnWake">true</bool> <bool name="config_proximityCheckOnWake">true</bool>
<bool name="config_proximityCheckOnWakeEnabledByDefault">true</bool> <bool name="config_proximityCheckOnWakeEnabledByDefault">true</bool>
</resources> </resources>

8
sepolicy/vendor/system_app.te vendored Normal file
View File

@@ -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);