OmniControl: Add charging control preferences
Change-Id: I936add4df464eb9818258de4955b651266c67ec3
Signed-off-by: micky387 <mickaelsaibi@free.fr>
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cd5a37c..0ea1f8d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -43,6 +43,18 @@
android:exported="false"
android:label="@string/color_activity_title"
android:launchMode="singleTop" />
+
+ <!-- Charging control settings (Battery category) -->
+ <activity
+ android:name=".ChargingControlSettings"
+ android:exported="true"
+ android:label="@string/charging_control_title">
+ <intent-filter>
+ <action android:name="org.omnirom.control.CHARGING_CONTROL_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/app/src/main/java/org/omnirom/control/ChargingControlSettings.java b/app/src/main/java/org/omnirom/control/ChargingControlSettings.java
new file mode 100644
index 0000000..821503a
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/ChargingControlSettings.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2023 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.omnirom.control;
+
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import org.omnirom.control.R;
+import org.omnirom.control.AbstractSettingsFragment;
+
+import org.omnirom.control.health.StartTimePreference;
+import org.omnirom.control.health.TargetTimePreference;
+import org.omnirom.control.health.ChargingLimitPreference;
+
+import omnirom.health.HealthInterface;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.XmlRes;
+import androidx.preference.ListPreference;
+import omnirom.preference.SystemSettingSwitchPreference;
+
+import static omnirom.health.HealthInterface.MODE_AUTO;
+import static omnirom.health.HealthInterface.MODE_MANUAL;
+import static omnirom.health.HealthInterface.MODE_LIMIT;
+
+import org.omnirom.omnilib.utils.OmniSettings;
+
+public class ChargingControlSettings extends AbstractSettingsFragment implements
+ Preference.OnPreferenceChangeListener {
+ private static final String TAG = ChargingControlSettings.class.getSimpleName();
+
+ public static final @DrawableRes int ICON_RES = R.drawable.ic_charging_control;
+ public static final @XmlRes int XML_RES = R.xml.charging_control_settings;
+ private static final String DEFAULT_VALUES = "reset_to_default_key";
+
+ private static final String CHARGING_CONTROL_ENABLED_PREF = "charging_control_enabled";
+ private static final String CHARGING_CONTROL_MODE_PREF = "charging_control_mode";
+ private static final String CHARGING_CONTROL_START_TIME_PREF = "charging_control_start_time";
+ private static final String CHARGING_CONTROL_TARGET_TIME_PREF = "charging_control_target_time";
+ private static final String CHARGING_CONTROL_LIMIT_PREF = "charging_control_charging_limit";
+
+ private Preference mDefaultValues;
+ private SystemSettingSwitchPreference mChargingControlEnabledPref;
+ private ListPreference mChargingControlModePref;
+ private StartTimePreference mChargingControlStartTimePref;
+ private TargetTimePreference mChargingControlTargetTimePref;
+ private ChargingLimitPreference mChargingControlLimitPref;
+
+ private HealthInterface mHealthInterface;
+
+ @Override
+ public String getFragmentTitle() {
+ return getString(R.string.charging_control_title);
+ }
+
+ @Override
+ public String getFragmentSummary() {
+ return getString(R.string.charging_control_summary);
+ }
+
+ @Override
+ public int getFragmentIcon() {
+ return ICON_RES;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(XML_RES, rootKey);
+ final Resources res = getResources();
+
+ mHealthInterface = HealthInterface.getInstance(getActivity());
+
+ final PreferenceScreen prefSet = getPreferenceScreen();
+
+ mChargingControlEnabledPref = prefSet.findPreference(CHARGING_CONTROL_ENABLED_PREF);
+ mChargingControlEnabledPref.setOnPreferenceChangeListener(this);
+ mChargingControlModePref = prefSet.findPreference(CHARGING_CONTROL_MODE_PREF);
+ mChargingControlModePref.setOnPreferenceChangeListener(this);
+ mChargingControlStartTimePref = prefSet.findPreference(CHARGING_CONTROL_START_TIME_PREF);
+ mChargingControlTargetTimePref = prefSet.findPreference(CHARGING_CONTROL_TARGET_TIME_PREF);
+ mChargingControlLimitPref = prefSet.findPreference(CHARGING_CONTROL_LIMIT_PREF);
+
+ if (mChargingControlLimitPref != null) {
+ if (mHealthInterface.allowFineGrainedSettings()) {
+ mChargingControlModePref.setEntries(concatStringArrays(
+ mChargingControlModePref.getEntries(),
+ res.getStringArray(
+ R.array.charging_control_mode_entries_fine_grained_control)));
+ mChargingControlModePref.setEntryValues(concatStringArrays(
+ mChargingControlModePref.getEntryValues(),
+ res.getStringArray(
+ R.array.charging_control_mode_values_fine_grained_control)));
+ }
+ }
+
+ refreshValues();
+
+ mDefaultValues = (Preference) findPreference(DEFAULT_VALUES);
+ mDefaultValues.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ resetToDefaults();
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ refreshUi();
+ }
+
+ private void refreshValues() {
+ if (mChargingControlEnabledPref != null) {
+ mChargingControlEnabledPref.setChecked(mHealthInterface.getEnabled());
+ }
+
+ if (mChargingControlModePref != null) {
+ final int chargingControlMode = mHealthInterface.getMode();
+ mChargingControlModePref.setValue(Integer.toString(chargingControlMode));
+ refreshUi();
+ }
+
+ if (mChargingControlStartTimePref != null) {
+ mChargingControlStartTimePref.setValue(
+ mChargingControlStartTimePref.getTimeSetting());
+ }
+
+ if (mChargingControlTargetTimePref != null) {
+ mChargingControlTargetTimePref.setValue(
+ mChargingControlTargetTimePref.getTimeSetting());
+ }
+
+ if (mChargingControlLimitPref != null) {
+ mChargingControlLimitPref.setValue(
+ mChargingControlLimitPref.getSetting());
+ }
+ }
+
+ private void refreshUi() {
+ final int chargingControlMode = mHealthInterface.getMode();
+
+ refreshUi(chargingControlMode);
+ }
+
+ private void refreshUi(final int chargingControlMode) {
+ String summary = null;
+ boolean isChargingControlStartTimePrefVisible = false;
+ boolean isChargingControlTargetTimePrefVisible = false;
+ boolean isChargingControlLimitPrefVisible = false;
+
+ final Resources res = getResources();
+
+ switch (chargingControlMode) {
+ case MODE_AUTO:
+ summary = res.getString(R.string.charging_control_mode_auto_summary);
+ break;
+ case MODE_MANUAL:
+ summary = res.getString(R.string.charging_control_mode_custom_summary);
+ isChargingControlStartTimePrefVisible = true;
+ isChargingControlTargetTimePrefVisible = true;
+ break;
+ case MODE_LIMIT:
+ summary = res.getString(R.string.charging_control_mode_limit_summary);
+ isChargingControlLimitPrefVisible = true;
+ break;
+ default:
+ return;
+ }
+
+ mChargingControlModePref.setSummary(summary);
+
+ if (mChargingControlStartTimePref != null) {
+ mChargingControlStartTimePref.setVisible(isChargingControlStartTimePrefVisible);
+ }
+
+ if (mChargingControlTargetTimePref != null) {
+ mChargingControlTargetTimePref.setVisible(isChargingControlTargetTimePrefVisible);
+ }
+
+ if (mChargingControlLimitPref != null) {
+ mChargingControlLimitPref.setVisible(isChargingControlLimitPrefVisible);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object objValue) {
+ if (preference == mChargingControlEnabledPref) {
+ mHealthInterface.setEnabled((Boolean) objValue);
+ } else if (preference == mChargingControlModePref) {
+ final int chargingControlMode = Integer.parseInt((String) objValue);
+ mHealthInterface.setMode(chargingControlMode);
+ refreshUi(chargingControlMode);
+ }
+ return true;
+ }
+
+ private void resetToDefaults() {
+ mHealthInterface.reset();
+
+ refreshValues();
+ }
+
+ private CharSequence[] concatStringArrays(CharSequence[] array1, CharSequence[] array2) {
+ return Stream.concat(Arrays.stream(array1), Arrays.stream(array2)).toArray(size ->
+ (CharSequence[]) Array.newInstance(CharSequence.class, size));
+ }
+}
diff --git a/app/src/main/java/org/omnirom/control/GridViewFragment.kt b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
index 5914e60..909cbb2 100644
--- a/app/src/main/java/org/omnirom/control/GridViewFragment.kt
+++ b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
@@ -21,6 +21,8 @@
import android.content.Context
import android.content.Intent
import android.os.Bundle
+import android.os.IBinder
+import android.os.ServiceManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -103,6 +105,16 @@
)
)
}
+ if (getLineageHealthStatus()) {
+ gridItems.add(
+ FragmentGridItem(
+ R.string.charging_control_title,
+ R.string.charging_control_summary,
+ R.drawable.ic_charging_control,
+ ChargingControlSettings()
+ )
+ )
+ }
gridItems.add(
FragmentGridItem(
R.string.bars_settings_title,
@@ -337,4 +349,19 @@
requireActivity().startActivity(gridItem.gridIntent)
}
}
+
+ private fun isNegated(key: String?): Boolean {
+ return key != null && key.startsWith("!")
+ }
+
+ private fun getLineageHealthStatus(): Boolean {
+ var rService = "lineagehealth"
+ val negated = isNegated(rService)
+ if (negated) {
+ rService = rService.substring(1)
+ }
+ val value: IBinder? = ServiceManager.getService(rService)
+ val available = value != null
+ return available != negated
+ }
}
diff --git a/app/src/main/java/org/omnirom/control/health/ChargingLimitPreference.java b/app/src/main/java/org/omnirom/control/health/ChargingLimitPreference.java
new file mode 100644
index 0000000..dde90c2
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/health/ChargingLimitPreference.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 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.omnirom.control.health;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import omnirom.health.HealthInterface;
+
+import org.omnirom.control.R;
+
+public class ChargingLimitPreference extends Preference
+ implements SeekBar.OnSeekBarChangeListener {
+ private static final String TAG = ChargingLimitPreference.class.getSimpleName();
+
+ private TextView mChargingLimitValue;
+ private SeekBar mChargingLimitBar;
+
+ private final HealthInterface mHealthInterface;
+
+ public ChargingLimitPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ setLayoutResource(R.layout.preference_charging_limit);
+
+ mHealthInterface = HealthInterface.getInstance(context);
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ mChargingLimitValue = (TextView) holder.findViewById(R.id.value);
+
+ mChargingLimitBar = (SeekBar) holder.findViewById(R.id.seekbar_widget);
+ mChargingLimitBar.setOnSeekBarChangeListener(this);
+
+ int currLimit = getSetting();
+ mChargingLimitBar.setProgress(currLimit);
+ updateValue(currLimit);
+ }
+
+ @Override
+ public void onStartTrackingTouch(final SeekBar seekBar) {
+ }
+
+ @Override
+ public void onStopTrackingTouch(final SeekBar seekBar) {
+ setSetting(seekBar.getProgress());
+ }
+
+ @Override
+ public void onProgressChanged(final SeekBar seekBar, final int progress,
+ final boolean fromUser) {
+ updateValue(progress);
+ }
+
+ public void setValue(final int value) {
+ if (mChargingLimitBar != null) {
+ mChargingLimitBar.setProgress(value);
+ }
+ updateValue(value);
+ }
+
+ public int getSetting() {
+ return mHealthInterface.getLimit();
+ }
+
+ protected void setSetting(final int chargingLimit) {
+ mHealthInterface.setLimit(chargingLimit);
+ }
+
+ private void updateValue(final int value) {
+ if (mChargingLimitValue != null) {
+ mChargingLimitValue.setText(String.format("%d%%", value));
+ }
+ }
+}
diff --git a/app/src/main/java/org/omnirom/control/health/StartTimePreference.java b/app/src/main/java/org/omnirom/control/health/StartTimePreference.java
new file mode 100644
index 0000000..59dee18
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/health/StartTimePreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.omnirom.control.health;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import org.omnirom.control.R;
+
+public class StartTimePreference extends TimePreference {
+ private static final String TAG = StartTimePreference.class.getSimpleName();
+
+ public StartTimePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected int getSummaryResourceId() {
+ return R.string.charging_control_start_time_summary;
+ }
+
+ @Override
+ public int getTimeSetting() {
+ return mHealthInterface.getStartTime();
+ }
+
+ @Override
+ protected void setTimeSetting(int secondOfDay) {
+ mHealthInterface.setStartTime(secondOfDay);
+ }
+}
diff --git a/app/src/main/java/org/omnirom/control/health/TargetTimePreference.java b/app/src/main/java/org/omnirom/control/health/TargetTimePreference.java
new file mode 100644
index 0000000..fbbd3a5
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/health/TargetTimePreference.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.omnirom.control.health;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import org.omnirom.control.R;
+
+public class TargetTimePreference extends TimePreference {
+ private static final String TAG = TargetTimePreference.class.getSimpleName();
+
+ public TargetTimePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected int getSummaryResourceId() {
+ return R.string.charging_control_target_time_summary;
+ }
+
+ @Override
+ public int getTimeSetting() {
+ return mHealthInterface.getTargetTime();
+ }
+
+ @Override
+ protected void setTimeSetting(int secondOfDay) {
+ mHealthInterface.setTargetTime(secondOfDay);
+ }
+}
diff --git a/app/src/main/java/org/omnirom/control/health/TimePreference.java b/app/src/main/java/org/omnirom/control/health/TimePreference.java
new file mode 100644
index 0000000..cde677d
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/health/TimePreference.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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.omnirom.control.health;
+
+import static java.time.format.FormatStyle.SHORT;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TimePicker;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceViewHolder;
+
+import org.omnirom.control.widget.CustomDialogPreference;
+import org.omnirom.control.R;
+
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+import omnirom.health.HealthInterface;
+
+public abstract class TimePreference extends CustomDialogPreference<AlertDialog> {
+ private static final String TAG = TimePreference.class.getSimpleName();
+ private static final DateTimeFormatter mFormatter = DateTimeFormatter.ofLocalizedTime(SHORT);
+
+ private TimePicker mTimePicker;
+ private LocalTime mLocalTime;
+
+ protected final HealthInterface mHealthInterface;
+
+ public TimePreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+
+ setDialogLayoutResource(R.layout.dialog_time);
+ mHealthInterface = HealthInterface.getInstance(context);
+ }
+
+ @Override
+ public void onBindViewHolder(final PreferenceViewHolder holder) {
+ mLocalTime = LocalTime.ofSecondOfDay(getTimeSetting());
+ super.onBindViewHolder(holder);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(final AlertDialog.Builder builder,
+ final DialogInterface.OnClickListener listener) {
+ super.onPrepareDialogBuilder(builder, listener);
+
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.dlg_ok, null);
+ }
+
+ @Override
+ protected void onDialogClosed(final boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult) {
+ mLocalTime = LocalTime.of(mTimePicker.getHour(),
+ mTimePicker.getMinute());
+ setTimeSetting(mLocalTime.toSecondOfDay());
+ setSummary(getSummary());
+ }
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ mTimePicker = view.findViewById(R.id.time_picker);
+ mTimePicker.setHour(mLocalTime.getHour());
+ mTimePicker.setMinute(mLocalTime.getMinute());
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return String.format(getContext().getString(getSummaryResourceId()),
+ mLocalTime.format(mFormatter));
+ }
+
+ public void setValue(final int value) {
+ mLocalTime = LocalTime.ofSecondOfDay(value);
+ setSummary(getSummary());
+ }
+
+ protected abstract int getSummaryResourceId();
+
+ protected abstract int getTimeSetting();
+
+ protected abstract void setTimeSetting(int secondOfDay);
+}
diff --git a/app/src/main/java/org/omnirom/control/widget/CustomDialogPreference.java b/app/src/main/java/org/omnirom/control/widget/CustomDialogPreference.java
new file mode 100644
index 0000000..ab9738b
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/widget/CustomDialogPreference.java
@@ -0,0 +1,205 @@
+/*
+ * SPDX-FileCopyrightText: 2015 The Android Open Source Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.omnirom.control.widget;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.PreferenceDialogFragmentCompat;
+import androidx.preference.DialogPreference;
+
+public class CustomDialogPreference<T extends DialogInterface> extends DialogPreference {
+
+ private CustomPreferenceDialogFragment mFragment;
+
+ public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public CustomDialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomDialogPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomDialogPreference(Context context) {
+ super(context);
+ }
+
+ public boolean isDialogOpen() {
+ return getDialog() != null && getDialog() instanceof Dialog && ((Dialog)getDialog()).isShowing();
+ }
+
+ public T getDialog() {
+ return (T) (mFragment != null ? mFragment.getDialog() : null);
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected void onClick(T dialog, int which) {
+ }
+
+ protected void onBindDialogView(View view) {
+ }
+
+ protected void onStart() {
+ }
+
+ protected void onStop() {
+ }
+
+ protected void onPause() {
+ }
+
+ protected void onResume() {
+ }
+
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return null;
+ }
+
+ protected View onCreateDialogView(Context context) {
+ return null;
+ }
+
+ private void setFragment(CustomPreferenceDialogFragment fragment) {
+ mFragment = fragment;
+ }
+
+ protected boolean onDismissDialog(T dialog, int which) {
+ return true;
+ }
+
+ public static class CustomPreferenceDialogFragment extends PreferenceDialogFragmentCompat {
+
+ public static CustomPreferenceDialogFragment newInstance(String key) {
+ final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ private CustomDialogPreference getCustomizablePreference() {
+ return (CustomDialogPreference) getPreference();
+ }
+
+ private class OnDismissListener implements View.OnClickListener {
+ private final int mWhich;
+ private final DialogInterface mDialog;
+
+ public OnDismissListener(DialogInterface dialog, int which) {
+ mWhich = which;
+ mDialog = dialog;
+ }
+
+ @Override
+ public void onClick(View view) {
+ CustomPreferenceDialogFragment.this.onClick(mDialog, mWhich);
+ if (getCustomizablePreference().onDismissDialog(mDialog, mWhich)) {
+ mDialog.dismiss();
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (getDialog() instanceof AlertDialog) {
+ AlertDialog a = (AlertDialog)getDialog();
+ if (a.getButton(Dialog.BUTTON_NEUTRAL) != null) {
+ a.getButton(Dialog.BUTTON_NEUTRAL).setOnClickListener(
+ new OnDismissListener(a, Dialog.BUTTON_NEUTRAL));
+ }
+ if (a.getButton(Dialog.BUTTON_POSITIVE) != null) {
+ a.getButton(Dialog.BUTTON_POSITIVE).setOnClickListener(
+ new OnDismissListener(a, Dialog.BUTTON_POSITIVE));
+ }
+ if (a.getButton(Dialog.BUTTON_NEGATIVE) != null) {
+ a.getButton(Dialog.BUTTON_NEGATIVE).setOnClickListener(
+ new OnDismissListener(a, Dialog.BUTTON_NEGATIVE));
+ }
+ }
+ getCustomizablePreference().onStart();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ getCustomizablePreference().onStop();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getCustomizablePreference().onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getCustomizablePreference().onResume();
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ getCustomizablePreference().setFragment(this);
+ getCustomizablePreference().onPrepareDialogBuilder(builder, this);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ getCustomizablePreference().onDialogClosed(positiveResult);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ getCustomizablePreference().onBindDialogView(view);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+ getCustomizablePreference().onClick(dialog, which);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ getCustomizablePreference().setFragment(this);
+ final Dialog sub = getCustomizablePreference().onCreateDialog(savedInstanceState);
+ if (sub == null) {
+ return super.onCreateDialog(savedInstanceState);
+ }
+ return sub;
+ }
+
+ @Override
+ protected View onCreateDialogView(Context context) {
+ final View v = getCustomizablePreference().onCreateDialogView(context);
+ if (v == null) {
+ return super.onCreateDialogView(context);
+ }
+ return v;
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/ic_charging_control.xml b/app/src/main/res/drawable/ic_charging_control.xml
new file mode 100644
index 0000000..70f7f39
--- /dev/null
+++ b/app/src/main/res/drawable/ic_charging_control.xml
@@ -0,0 +1,17 @@
+<!--
+ SPDX-FileCopyrightText: 2018 The Android Open Source Project
+ SPDX-License-Identifier: Apache-2.0
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:textColorPrimary"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M16.2,22.5H7.8c-1.3,0 -2.3,-1 -2.3,-2.3V5.8c0,-1.3 1,-2.3 2.3,-2.3h0.7v-2h7v2h0.7c1.3,0 2.3,1.1 2.3,2.3v14.3C18.5,21.5 17.5,22.5 16.2,22.5zM7.8,5.5c-0.2,0 -0.3,0.2 -0.3,0.3v14.3c0,0.2 0.2,0.3 0.3,0.3h8.3c0.2,0 0.3,-0.1 0.3,-0.3V5.8c0,-0.2 -0.1,-0.3 -0.3,-0.3h-2.7v-2h-3v2H7.8z"/>
+ <path
+ android:fillColor="?android:attr/colorPrimary"
+ android:pathData="M11.17,18.42v-4.58H9.5l3.33,-6.25v4.58h1.67L11.17,18.42z"/>
+</vector>
diff --git a/app/src/main/res/layout/dialog_time.xml b/app/src/main/res/layout/dialog_time.xml
new file mode 100644
index 0000000..3572317
--- /dev/null
+++ b/app/src/main/res/layout/dialog_time.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:orientation="vertical">
+
+ <TimePicker
+ android:id="@+id/time_picker"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingTop="8dp" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/preference_charging_limit.xml b/app/src/main/res/layout/preference_charging_limit.xml
new file mode 100644
index 0000000..9c2b4fe
--- /dev/null
+++ b/app/src/main/res/layout/preference_charging_limit.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:paddingVertical="6dip"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="0dp"
+ android:layout_weight="1">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorPrimary"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView
+ android:id="@+id/value"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentEnd="true"
+ android:layout_centerVertical="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:singleLine="true" />
+
+ </RelativeLayout>
+
+ <SeekBar
+ android:id="@+id/seekbar_widget"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingVertical="15dip"
+ android:min="70"
+ android:max="100" />
+
+</LinearLayout>
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index bc76d8b..5823a9b 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1,4 +1,21 @@
<resources>
+ <!-- Values for the charging control modes -->
+ <string-array name="charging_control_mode_entries" translatable="false">
+ <item>@string/charging_control_mode_auto_title</item>
+ <item>@string/charging_control_mode_custom_title</item>
+ </string-array>
+ <string-array name="charging_control_mode_values" translatable="false">
+ <item>1</item>
+ <item>2</item>
+ </string-array>
+ <!-- Values for the additional charging control modes if fine-grained control is supported -->
+ <string-array name="charging_control_mode_entries_fine_grained_control" translatable="false">
+ <item>@string/charging_control_mode_limit_title</item>
+ </string-array>
+ <string-array name="charging_control_mode_values_fine_grained_control" translatable="false">
+ <item>3</item>
+ </string-array>
+
<!-- LED behavior when battery is low -->
<string-array name="led_battery_entries" translatable="false">
<item>Solid when charging, flashing when not charging</item>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4cca5ed..9ba14ac 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -50,6 +50,26 @@
<string name="device_settings_title">Device settings</string>
<string name="device_settings_summary">Advanced device specific options</string>
+ <!-- Charging control settings -->
+ <string name="charging_control_title">Charging control</string>
+ <string name="charging_control_summary">Charging control settings</string>
+ <string name="charging_control_enable_title">Enable charging control</string>
+ <string name="charging_control_mode_title">Charging mode</string>
+ <string name="charging_control_mode_auto_title">Automatic schedule</string>
+ <string name="charging_control_mode_auto_summary">Automatically determine when to start charging based on alarms set</string>
+ <string name="charging_control_mode_custom_title">Custom schedule</string>
+ <string name="charging_control_mode_custom_summary">Set a target time to full charge</string>
+ <string name="charging_control_mode_limit_title">Limit charging</string>
+ <string name="charging_control_mode_limit_summary">Limit charging to a certain percentage</string>
+ <string name="charging_control_start_time_title">Start time</string>
+ <string name="charging_control_start_time_summary">Charging control activates when you start charging after %s</string>
+ <string name="charging_control_target_time_title">Target time to full charge</string>
+ <string name="charging_control_target_time_summary">Battery will be fully charged by %s</string>
+ <string name="charging_control_limit_title">Limit</string>
+ <string name="disabled">Disabled</string>
+ <string name="dlg_ok">OK</string>
+ <string name="enabled">Enabled</string>
+
<!-- doze on charge -->
<string name="doze_on_charge_title">Show ambient display when charging</string>
<string name="doze_on_charge_summary">Wake screen when charging</string>
diff --git a/app/src/main/res/xml/charging_control_settings.xml b/app/src/main/res/xml/charging_control_settings.xml
new file mode 100644
index 0000000..bc0098f
--- /dev/null
+++ b/app/src/main/res/xml/charging_control_settings.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2023 The LineageOS Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="charging_control"
+ android:title="@string/charging_control_title">
+
+ <Preference
+ android:title="@string/reset_to_default"
+ android:widgetLayout="@layout/reset_default"
+ android:key="reset_to_default_key" />
+
+ <omnirom.preference.SystemSettingSwitchPreference
+ android:key="charging_control_enabled"
+ android:title="@string/charging_control_enable_title" />
+
+ <ListPreference
+ android:key="charging_control_mode"
+ android:title="@string/charging_control_mode_title"
+ android:entries="@array/charging_control_mode_entries"
+ android:entryValues="@array/charging_control_mode_values"
+ android:dependency="charging_control_enabled" />
+
+ <org.omnirom.control.health.StartTimePreference
+ android:key="charging_control_start_time"
+ android:title="@string/charging_control_start_time_title"
+ android:dependency="charging_control_enabled" />
+
+ <org.omnirom.control.health.TargetTimePreference
+ android:key="charging_control_target_time"
+ android:title="@string/charging_control_target_time_title"
+ android:dependency="charging_control_enabled" />
+
+ <org.omnirom.control.health.ChargingLimitPreference
+ android:key="charging_control_charging_limit"
+ android:title="@string/charging_control_limit_title"
+ android:dependency="charging_control_enabled" />
+
+</PreferenceScreen>