sdk: Implement Lineage health service
Change-Id: I772ccf6d323c24d681aa8468bf4318c7b73bd3f5
This commit is contained in:
committed by
Michael Bestas
parent
89941a9622
commit
3ee210210d
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright (C) 2018-2022 The LineageOS Project
|
||||
// Copyright (C) 2018-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.
|
||||
@@ -66,6 +66,7 @@ lineage_sdk_internal_src = "sdk/src/java/org/lineageos/internal"
|
||||
library_src = "lineage/lib/main/java"
|
||||
|
||||
lineage_sdk_LOCAL_STATIC_JAVA_LIBRARIES = [
|
||||
"vendor.lineage.health-V1-java",
|
||||
"vendor.lineage.livedisplay-V2.0-java",
|
||||
"vendor.lineage.livedisplay-V2.1-java",
|
||||
"vendor.lineage.touch-V1.0-java",
|
||||
|
||||
@@ -0,0 +1,873 @@
|
||||
/*
|
||||
* 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.lineageos.platform.internal.health;
|
||||
|
||||
import static java.time.format.FormatStyle.SHORT;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.Uri;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.BatteryStatsManager;
|
||||
import android.os.BatteryUsageStats;
|
||||
import android.os.Handler;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.lineageos.platform.internal.R;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Calendar;
|
||||
|
||||
import lineageos.providers.LineageSettings;
|
||||
|
||||
import vendor.lineage.health.ChargingControlSupportedMode;
|
||||
import vendor.lineage.health.IChargingControl;
|
||||
|
||||
import static lineageos.health.HealthInterface.MODE_NONE;
|
||||
import static lineageos.health.HealthInterface.MODE_AUTO;
|
||||
import static lineageos.health.HealthInterface.MODE_MANUAL;
|
||||
import static lineageos.health.HealthInterface.MODE_LIMIT;
|
||||
|
||||
public class ChargingControlController extends LineageHealthFeature {
|
||||
private final IChargingControl mChargingControl;
|
||||
private final ContentResolver mContentResolver;
|
||||
private final ChargingControlNotification mChargingNotification;
|
||||
private LineageHealthBatteryBroadcastReceiver mBattReceiver;
|
||||
|
||||
// Defaults
|
||||
private final boolean mDefaultEnabled;
|
||||
private final int mDefaultMode;
|
||||
private final int mDefaultLimit;
|
||||
private final int mDefaultStartTime;
|
||||
private final int mDefaultTargetTime;
|
||||
|
||||
// User configs
|
||||
private boolean mConfigEnabled = false;
|
||||
private int mConfigMode = MODE_NONE;
|
||||
private int mConfigLimit = 100;
|
||||
private int mConfigStartTime = 0;
|
||||
private int mConfigTargetTime = 0;
|
||||
|
||||
// Settings uris
|
||||
private final Uri MODE_URI = LineageSettings.System.getUriFor(
|
||||
LineageSettings.System.CHARGING_CONTROL_MODE);
|
||||
private final Uri LIMIT_URI = LineageSettings.System.getUriFor(
|
||||
LineageSettings.System.CHARGING_CONTROL_LIMIT);
|
||||
private final Uri ENABLED_URI = LineageSettings.System.getUriFor(
|
||||
LineageSettings.System.CHARGING_CONTROL_ENABLED);
|
||||
private final Uri START_TIME_URI = LineageSettings.System.getUriFor(
|
||||
LineageSettings.System.CHARGING_CONTROL_START_TIME);
|
||||
private final Uri TARGET_TIME_URI = LineageSettings.System.getUriFor(
|
||||
LineageSettings.System.CHARGING_CONTROL_TARGET_TIME);
|
||||
|
||||
// Internal state
|
||||
private float mBatteryPct = 0;
|
||||
private boolean mIsPowerConnected = false;
|
||||
private int mChargingStopReason = 0;
|
||||
private long mEstimatedFullTime = 0;
|
||||
private long mSavedAlarmTime = 0;
|
||||
private long mSavedTargetTime = 0;
|
||||
private boolean mIsControlCancelledOnce = false;
|
||||
private final boolean mIsChargingToggleSupported;
|
||||
private final boolean mIsChargingBypassSupported;
|
||||
private final boolean mIsChargingDeadlineSupported;
|
||||
private final int mChargingTimeMargin;
|
||||
private final int mChargingLimitMargin;
|
||||
|
||||
private static final DateTimeFormatter mFormatter = DateTimeFormatter.ofLocalizedTime(SHORT);
|
||||
private static final SimpleDateFormat mDateFormatter = new SimpleDateFormat("hh:mm:ss a");
|
||||
|
||||
// Only when the battery level is above this limit will the charging control be activated.
|
||||
private static int CHARGE_CTRL_MIN_LEVEL = 80;
|
||||
private static final String INTENT_PARTS =
|
||||
"org.lineageos.lineageparts.CHARGING_CONTROL_SETTINGS";
|
||||
|
||||
private static class ChargingStopReason {
|
||||
private static int BIT(int shift) {
|
||||
return 1 << shift;
|
||||
}
|
||||
|
||||
/**
|
||||
* No stop charging
|
||||
*/
|
||||
public static final int NONE = 0;
|
||||
|
||||
/**
|
||||
* The charging stopped because it reaches limit
|
||||
*/
|
||||
public static final int REACH_LIMIT = BIT(0);
|
||||
|
||||
/**
|
||||
* The charging stopped because the battery level is decent, and we are waiting to resume
|
||||
* charging when the time approaches the target time.
|
||||
*/
|
||||
public static final int WAITING = BIT(1);
|
||||
}
|
||||
|
||||
public ChargingControlController(Context context, Handler handler) {
|
||||
super(context, handler);
|
||||
|
||||
mContentResolver = mContext.getContentResolver();
|
||||
mChargingControl = IChargingControl.Stub.asInterface(
|
||||
ServiceManager.getService(IChargingControl.DESCRIPTOR + "/default"));
|
||||
|
||||
if (mChargingControl == null) {
|
||||
Log.i(TAG, "Lineage Health HAL not found");
|
||||
}
|
||||
|
||||
mChargingNotification = new ChargingControlNotification(context);
|
||||
|
||||
mChargingTimeMargin = mContext.getResources().getInteger(
|
||||
R.integer.config_chargingControlTimeMargin) * 60 * 1000;
|
||||
mChargingLimitMargin = mContext.getResources().getInteger(
|
||||
R.integer.config_chargingControlBatteryRechargeMargin);
|
||||
|
||||
mDefaultEnabled = mContext.getResources().getBoolean(
|
||||
R.bool.config_chargingControlEnabled);
|
||||
mDefaultMode = mContext.getResources().getInteger(
|
||||
R.integer.config_defaultChargingControlMode);
|
||||
mDefaultStartTime = mContext.getResources().getInteger(
|
||||
R.integer.config_defaultChargingControlStartTime);
|
||||
mDefaultTargetTime = mContext.getResources().getInteger(
|
||||
R.integer.config_defaultChargingControlTargetTime);
|
||||
mDefaultLimit = mContext.getResources().getInteger(
|
||||
R.integer.config_defaultChargingControlLimit);
|
||||
|
||||
mIsChargingToggleSupported = isChargingModeSupported(ChargingControlSupportedMode.TOGGLE);
|
||||
mIsChargingBypassSupported = isChargingModeSupported(ChargingControlSupportedMode.BYPASS);
|
||||
mIsChargingDeadlineSupported = isChargingModeSupported(
|
||||
ChargingControlSupportedMode.DEADLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return mChargingControl != null;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return mConfigEnabled;
|
||||
}
|
||||
|
||||
public boolean setEnabled(boolean enabled) {
|
||||
putBoolean(LineageSettings.System.CHARGING_CONTROL_ENABLED, enabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getMode() {
|
||||
return mConfigMode;
|
||||
}
|
||||
|
||||
public boolean setMode(int mode) {
|
||||
if (mode < MODE_NONE || mode > MODE_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
putInt(LineageSettings.System.CHARGING_CONTROL_MODE, mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getStartTime() {
|
||||
return mConfigStartTime;
|
||||
}
|
||||
|
||||
public boolean setStartTime(int time) {
|
||||
if (time < 0 || time > 24 * 60 * 60) {
|
||||
return false;
|
||||
}
|
||||
|
||||
putInt(LineageSettings.System.CHARGING_CONTROL_START_TIME, time);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getTargetTime() {
|
||||
return mConfigTargetTime;
|
||||
}
|
||||
|
||||
public boolean setTargetTime(int time) {
|
||||
if (time < 0 || time > 24 * 60 * 60) {
|
||||
return false;
|
||||
}
|
||||
|
||||
putInt(LineageSettings.System.CHARGING_CONTROL_TARGET_TIME, time);
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return mConfigLimit;
|
||||
}
|
||||
|
||||
public boolean setLimit(int limit) {
|
||||
if (limit < 0 || limit > 100) {
|
||||
return false;
|
||||
}
|
||||
|
||||
putInt(LineageSettings.System.CHARGING_CONTROL_LIMIT, limit);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean reset() {
|
||||
return setEnabled(mDefaultEnabled) && setMode(mDefaultMode) && setLimit(mDefaultLimit)
|
||||
&& setStartTime(mDefaultStartTime) && setTargetTime(mDefaultTargetTime);
|
||||
}
|
||||
|
||||
public boolean isChargingModeSupported(int mode) {
|
||||
try {
|
||||
return (mChargingControl.getSupportedMode() & mode) != 0;
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (mChargingControl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register setting observer
|
||||
registerSettings(MODE_URI, LIMIT_URI, ENABLED_URI, START_TIME_URI, TARGET_TIME_URI);
|
||||
|
||||
// For devices that do not support bypass, we can only always listen to battery change
|
||||
// because we can't distinguish between "unplugged" and "plugged in but not charging".
|
||||
if (mIsChargingToggleSupported && !mIsChargingBypassSupported) {
|
||||
mIsPowerConnected = true;
|
||||
onPowerStatus(true);
|
||||
handleSettingChange();
|
||||
return;
|
||||
}
|
||||
|
||||
// Start monitor battery status when power connected
|
||||
IntentFilter connectedFilter = new IntentFilter(Intent.ACTION_POWER_CONNECTED);
|
||||
mContext.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "Power connected, start monitoring battery");
|
||||
mIsPowerConnected = true;
|
||||
onPowerStatus(true);
|
||||
}
|
||||
}, connectedFilter);
|
||||
|
||||
// Stop monitor battery status when power disconnected
|
||||
IntentFilter disconnectedFilter = new IntentFilter(Intent.ACTION_POWER_DISCONNECTED);
|
||||
mContext.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "Power disconnected, stop monitoring battery");
|
||||
mIsPowerConnected = false;
|
||||
onPowerStatus(false);
|
||||
}
|
||||
}, disconnectedFilter);
|
||||
|
||||
// Initial monitor
|
||||
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
Intent batteryStatus = mContext.registerReceiver(null, ifilter);
|
||||
mIsPowerConnected = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) != 0;
|
||||
if (mIsPowerConnected) {
|
||||
onPowerConnected();
|
||||
}
|
||||
|
||||
// Restore settings
|
||||
handleSettingChange();
|
||||
}
|
||||
|
||||
private void resetInternalState() {
|
||||
mSavedAlarmTime = 0;
|
||||
mSavedTargetTime = 0;
|
||||
mEstimatedFullTime = 0;
|
||||
mChargingStopReason = 0;
|
||||
mIsControlCancelledOnce = false;
|
||||
mChargingNotification.cancel();
|
||||
}
|
||||
|
||||
private void onPowerConnected() {
|
||||
if (mBattReceiver == null) {
|
||||
mBattReceiver = new LineageHealthBatteryBroadcastReceiver();
|
||||
}
|
||||
IntentFilter battFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
mContext.registerReceiver(mBattReceiver, battFilter);
|
||||
}
|
||||
|
||||
private void onPowerDisconnected() {
|
||||
if (mBattReceiver != null) {
|
||||
mContext.unregisterReceiver(mBattReceiver);
|
||||
}
|
||||
|
||||
// On disconnected, reset internal state
|
||||
resetInternalState();
|
||||
}
|
||||
|
||||
private void onPowerStatus(boolean enable) {
|
||||
if (enable) {
|
||||
onPowerConnected();
|
||||
} else {
|
||||
onPowerDisconnected();
|
||||
}
|
||||
|
||||
updateChargeControl();
|
||||
}
|
||||
|
||||
private void updateChargingReasonBitmask(int flag, boolean set) {
|
||||
if (set) {
|
||||
mChargingStopReason |= flag;
|
||||
} else {
|
||||
mChargingStopReason &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isChargingReasonSet(int flag) {
|
||||
return (mChargingStopReason & flag) != 0;
|
||||
}
|
||||
|
||||
private ChargeTime getChargeTime() {
|
||||
// Get duration to target full time
|
||||
final long currentTime = System.currentTimeMillis();
|
||||
Log.i(TAG, "Current time is " + msToString(currentTime));
|
||||
long targetTime = 0, startTime = currentTime;
|
||||
if (mConfigMode == MODE_AUTO) {
|
||||
// Use alarm as the target time. Maybe someday we can use a model.
|
||||
AlarmManager m = mContext.getSystemService(AlarmManager.class);
|
||||
if (m == null) {
|
||||
Log.e(TAG, "Failed to get alarm service!");
|
||||
mChargingNotification.cancel();
|
||||
return null;
|
||||
}
|
||||
AlarmManager.AlarmClockInfo alarmClockInfo = m.getNextAlarmClock();
|
||||
if (alarmClockInfo == null) {
|
||||
// We didn't find an alarm. Clear waiting flags because we can't predict anyway
|
||||
mChargingNotification.cancel();
|
||||
return null;
|
||||
}
|
||||
targetTime = alarmClockInfo.getTriggerTime();
|
||||
} else if (mConfigMode == MODE_MANUAL) {
|
||||
// User manually controlled time
|
||||
startTime = getTimeMillisFromSecondOfDay(mConfigStartTime);
|
||||
targetTime = getTimeMillisFromSecondOfDay(mConfigTargetTime);
|
||||
|
||||
if (startTime > targetTime) {
|
||||
if (currentTime > targetTime) {
|
||||
targetTime += DateUtils.DAY_IN_MILLIS;
|
||||
} else {
|
||||
startTime -= DateUtils.DAY_IN_MILLIS;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "invalid charging control mode " + mConfigMode);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ChargeTime(startTime, targetTime);
|
||||
}
|
||||
|
||||
private void updateChargeControl() {
|
||||
if (mIsChargingToggleSupported) {
|
||||
updateChargeToggle();
|
||||
} else if (mIsChargingDeadlineSupported) {
|
||||
updateChargeDeadline();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSetLimitFlag() {
|
||||
if (mConfigMode != MODE_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mIsChargingBypassSupported
|
||||
&& isChargingReasonSet(ChargingStopReason.REACH_LIMIT)) {
|
||||
return mBatteryPct >= mConfigLimit - mChargingLimitMargin;
|
||||
}
|
||||
|
||||
if (mBatteryPct >= mConfigLimit) {
|
||||
mChargingNotification.post(null, true);
|
||||
return true;
|
||||
} else {
|
||||
mChargingNotification.post(null, false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSetWaitFlag() {
|
||||
if (mConfigMode != MODE_AUTO && mConfigMode != MODE_MANUAL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now it is time to see whether charging should be stopped. We make decisions in the
|
||||
// following manner:
|
||||
//
|
||||
// 1. If STOP_REASON_WAITING is set, compare the remaining time with the saved estimated
|
||||
// full time. Resume charging the remain time <= saved estimated time
|
||||
// 2. If the system estimated remaining time already exceeds the target full time, continue
|
||||
// 3. Otherwise, stop charging, save the estimated time, set stop reason to
|
||||
// STOP_REASON_WAITING.
|
||||
|
||||
final ChargeTime t = getChargeTime();
|
||||
|
||||
if (t == null) {
|
||||
mChargingNotification.cancel();
|
||||
return false;
|
||||
}
|
||||
|
||||
final long targetTime = t.getTargetTime();
|
||||
final long startTime = t.getStartTime();
|
||||
final long currentTime = System.currentTimeMillis();
|
||||
|
||||
Log.i(TAG, "Got target time " + msToString(targetTime) + ", start time " +
|
||||
msToString(startTime) + ", current time " + msToString(currentTime));
|
||||
|
||||
if (mConfigMode == MODE_AUTO) {
|
||||
if (mSavedAlarmTime != targetTime) {
|
||||
mChargingNotification.cancel();
|
||||
|
||||
if (mSavedAlarmTime != 0 && mSavedAlarmTime < currentTime) {
|
||||
Log.i(TAG, "Not fully charged when alarm goes off, continue charging.");
|
||||
mIsControlCancelledOnce = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.i(TAG, "User changed alarm, reconstruct notification");
|
||||
mSavedAlarmTime = targetTime;
|
||||
}
|
||||
|
||||
// Don't activate if we are more than 9 hrs away from the target alarm
|
||||
if (targetTime - currentTime >= 9 * 60 * 60 * 1000) {
|
||||
mChargingNotification.cancel();
|
||||
return false;
|
||||
}
|
||||
} else if (mConfigMode == MODE_MANUAL) {
|
||||
if (startTime > currentTime) {
|
||||
// Not yet entering user configured time frame
|
||||
mChargingNotification.cancel();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (mBatteryPct == 100) {
|
||||
mChargingNotification.post(targetTime, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now we have the target time and current time, we can post a notification stating that
|
||||
// the system will be charged by targetTime.
|
||||
mChargingNotification.post(targetTime, false);
|
||||
|
||||
// If current battery level is less than the fast charge limit, don't set this flag
|
||||
if (mBatteryPct < CHARGE_CTRL_MIN_LEVEL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
long deltaTime = targetTime - currentTime;
|
||||
Log.i(TAG, "Current time to target: " + msToString(deltaTime));
|
||||
|
||||
if (isChargingReasonSet(ChargingStopReason.WAITING)) {
|
||||
Log.i(TAG, "Current saved estimation to full: " + msToString(mEstimatedFullTime));
|
||||
if (deltaTime <= mEstimatedFullTime) {
|
||||
Log.i(TAG, "Unset waiting flag");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
final BatteryUsageStats batteryUsageStats = mContext.getSystemService(
|
||||
BatteryStatsManager.class).getBatteryUsageStats();
|
||||
if (batteryUsageStats == null) {
|
||||
Log.e(TAG, "Failed to get battery usage stats");
|
||||
return false;
|
||||
}
|
||||
long remaining = batteryUsageStats.getChargeTimeRemainingMs();
|
||||
if (remaining == -1) {
|
||||
Log.i(TAG, "not enough data for prediction for now, waiting for more data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add margin here
|
||||
remaining += mChargingTimeMargin;
|
||||
Log.i(TAG, "Current estimated time to full: " + msToString(remaining));
|
||||
if (deltaTime > remaining) {
|
||||
Log.i(TAG, "Stop charging and wait, saving remaining time");
|
||||
mEstimatedFullTime = remaining;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateChargingStopReason() {
|
||||
if (mIsControlCancelledOnce) {
|
||||
mChargingStopReason = ChargingStopReason.NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mConfigEnabled) {
|
||||
mChargingStopReason = ChargingStopReason.NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mIsPowerConnected) {
|
||||
mChargingStopReason = ChargingStopReason.NONE;
|
||||
return;
|
||||
}
|
||||
|
||||
updateChargingReasonBitmask(ChargingStopReason.REACH_LIMIT, shouldSetLimitFlag());
|
||||
updateChargingReasonBitmask(ChargingStopReason.WAITING, shouldSetWaitFlag());
|
||||
}
|
||||
|
||||
private void updateChargeToggle() {
|
||||
updateChargingStopReason();
|
||||
|
||||
Log.i(TAG, "Current mChargingStopReason: " + mChargingStopReason);
|
||||
boolean isChargingEnabled = false;
|
||||
try {
|
||||
isChargingEnabled = mChargingControl.getChargingEnabled();
|
||||
} catch (IllegalStateException | RemoteException | UnsupportedOperationException e) {
|
||||
Log.e(TAG, "Failed to get charging enabled status!");
|
||||
}
|
||||
if (isChargingEnabled != (mChargingStopReason == 0)) {
|
||||
try {
|
||||
mChargingControl.setChargingEnabled(!isChargingEnabled);
|
||||
} catch (IllegalStateException | RemoteException | UnsupportedOperationException e) {
|
||||
Log.e(TAG, "Failed to set charging status");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChargeDeadline() {
|
||||
if (!mIsPowerConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ChargeTime t = getChargeTime();
|
||||
if (t != null && t.getTargetTime() == mSavedTargetTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
long deadline = 0;
|
||||
if (t == null || mIsControlCancelledOnce) {
|
||||
deadline = -1;
|
||||
} else {
|
||||
mSavedTargetTime = t.getTargetTime();
|
||||
final long targetTime = t.getTargetTime();
|
||||
final long currentTime = System.currentTimeMillis();
|
||||
deadline = (targetTime - currentTime) / 1000;
|
||||
}
|
||||
|
||||
try {
|
||||
mChargingControl.setChargingDeadline(deadline);
|
||||
} catch (IllegalStateException | RemoteException | UnsupportedOperationException e) {
|
||||
Log.e(TAG, "Failed to set charge deadline");
|
||||
}
|
||||
}
|
||||
|
||||
private String msToString(long ms) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(ms);
|
||||
return mDateFormatter.format(calendar.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the seconds of the day to UTC milliseconds from epoch.
|
||||
*
|
||||
* @param time seconds of the day
|
||||
* @return UTC milliseconds from epoch
|
||||
*/
|
||||
private long getTimeMillisFromSecondOfDay(int time) {
|
||||
ZoneId utcZone = ZoneOffset.UTC;
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
LocalTime timeOfDay = LocalTime.ofSecondOfDay(time);
|
||||
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.of(currentDate, timeOfDay,
|
||||
ZoneId.systemDefault())
|
||||
.withZoneSameInstant(utcZone);
|
||||
return zonedDateTime.toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
private LocalTime getLocalTimeFromEpochMilli(long time) {
|
||||
return Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).toLocalTime();
|
||||
}
|
||||
|
||||
private void handleSettingChange() {
|
||||
mConfigEnabled = LineageSettings.System.getInt(mContentResolver,
|
||||
LineageSettings.System.CHARGING_CONTROL_ENABLED, 0)
|
||||
!= 0;
|
||||
mConfigLimit = LineageSettings.System.getInt(mContentResolver,
|
||||
LineageSettings.System.CHARGING_CONTROL_LIMIT,
|
||||
mDefaultLimit);
|
||||
mConfigMode = LineageSettings.System.getInt(mContentResolver,
|
||||
LineageSettings.System.CHARGING_CONTROL_MODE,
|
||||
mDefaultMode);
|
||||
mConfigStartTime = LineageSettings.System.getInt(mContentResolver,
|
||||
LineageSettings.System.CHARGING_CONTROL_START_TIME,
|
||||
mDefaultStartTime);
|
||||
mConfigTargetTime = LineageSettings.System.getInt(mContentResolver,
|
||||
LineageSettings.System.CHARGING_CONTROL_TARGET_TIME,
|
||||
mDefaultTargetTime);
|
||||
|
||||
// Cancel notification, so that it can be updated later
|
||||
mChargingNotification.cancel();
|
||||
|
||||
// Update based on those values
|
||||
updateChargeControl();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onSettingsChanged(Uri uri) {
|
||||
handleSettingChange();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(PrintWriter pw) {
|
||||
pw.println();
|
||||
pw.println("ChargingControlController Configuration:");
|
||||
pw.println(" mConfigEnabled: " + mConfigEnabled);
|
||||
pw.println(" mConfigMode: " + mConfigMode);
|
||||
pw.println(" mConfigLimit: " + mConfigLimit);
|
||||
pw.println(" mConfigStartTime: " + mConfigStartTime);
|
||||
pw.println(" mConfigTargetTime: " + mConfigTargetTime);
|
||||
pw.println(" mChargingTimeMargin: " + mChargingTimeMargin);
|
||||
pw.println();
|
||||
pw.println("ChargingControlController State:");
|
||||
pw.println(" mBatteryPct: " + mBatteryPct);
|
||||
pw.println(" mIsPowerConnected: " + mIsPowerConnected);
|
||||
pw.println(" mChargingStopReason: " + mChargingStopReason);
|
||||
pw.println(" mIsNotificationPosted: " + mChargingNotification.isPosted());
|
||||
pw.println(" mIsDoneNotification: " + mChargingNotification.isDoneNotification());
|
||||
pw.println(" mIsControlCancelledOnce: " + mIsControlCancelledOnce);
|
||||
pw.println(" mSavedAlarmTime: " + msToString(mSavedAlarmTime));
|
||||
if (mIsChargingDeadlineSupported) {
|
||||
pw.println(" mSavedTargetTime (Deadline): " + msToString(mSavedTargetTime));
|
||||
}
|
||||
}
|
||||
|
||||
/* Battery Broadcast Receiver */
|
||||
private class LineageHealthBatteryBroadcastReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!mIsPowerConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||
int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
||||
if (level == -1 || scale == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBatteryPct = level * 100 / (float) scale;
|
||||
updateChargeControl();
|
||||
}
|
||||
}
|
||||
|
||||
/* Notification class */
|
||||
class ChargingControlNotification {
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final Context mContext;
|
||||
|
||||
private static final int CHARGING_CONTROL_NOTIFICATION_ID = 1000;
|
||||
private static final String ACTION_CHARGING_CONTROL_CANCEL_ONCE =
|
||||
"lineageos.platform.intent.action.CHARGING_CONTROL_CANCEL_ONCE";
|
||||
private static final String CHARGING_CONTROL_CHANNEL_ID = "LineageHealthChargingControl";
|
||||
|
||||
private boolean mIsDoneNotification = false;
|
||||
private boolean mIsNotificationPosted = false;
|
||||
|
||||
ChargingControlNotification(Context context) {
|
||||
mContext = context;
|
||||
|
||||
// Get notification manager
|
||||
mNotificationManager = mContext.getSystemService(NotificationManager.class);
|
||||
|
||||
// Register notification monitor
|
||||
IntentFilter notificationFilter = new IntentFilter(ACTION_CHARGING_CONTROL_CANCEL_ONCE);
|
||||
mContext.registerReceiver(new LineageHealthNotificationBroadcastReceiver(),
|
||||
notificationFilter);
|
||||
}
|
||||
|
||||
public void post(Long targetTime, boolean done) {
|
||||
if (mIsNotificationPosted && mIsDoneNotification == done) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mIsNotificationPosted) {
|
||||
cancel();
|
||||
}
|
||||
|
||||
if (done) {
|
||||
postChargingDoneNotification(targetTime);
|
||||
} else {
|
||||
postChargingControlNotification(targetTime);
|
||||
}
|
||||
|
||||
mIsNotificationPosted = true;
|
||||
mIsDoneNotification = done;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
cancelChargingControlNotification();
|
||||
mIsNotificationPosted = false;
|
||||
}
|
||||
|
||||
public boolean isPosted() {
|
||||
return mIsNotificationPosted;
|
||||
}
|
||||
|
||||
public boolean isDoneNotification() {
|
||||
return mIsDoneNotification;
|
||||
}
|
||||
|
||||
private void handleNotificationIntent(Intent intent) {
|
||||
if (intent.getAction().equals(ACTION_CHARGING_CONTROL_CANCEL_ONCE)) {
|
||||
mIsControlCancelledOnce = true;
|
||||
updateChargeControl();
|
||||
cancelChargingControlNotification();
|
||||
}
|
||||
}
|
||||
|
||||
private void postChargingControlNotification(Long targetTime) {
|
||||
String title = mContext.getString(R.string.charging_control_notification_title);
|
||||
String message;
|
||||
if (targetTime != null) {
|
||||
message = String.format(
|
||||
mContext.getString(R.string.charging_control_notification_content_target),
|
||||
getLocalTimeFromEpochMilli(targetTime).format(mFormatter));
|
||||
} else {
|
||||
message = String.format(
|
||||
mContext.getString(R.string.charging_control_notification_content_limit),
|
||||
mConfigLimit);
|
||||
}
|
||||
|
||||
Intent mainIntent = new Intent(INTENT_PARTS);
|
||||
mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent mainPendingIntent = PendingIntent.getActivity(mContext, 0, mainIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Intent cancelOnceIntent = new Intent(ACTION_CHARGING_CONTROL_CANCEL_ONCE);
|
||||
PendingIntent cancelPendingIntent = PendingIntent.getBroadcast(mContext, 0,
|
||||
cancelOnceIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Notification.Builder notification =
|
||||
new Notification.Builder(mContext, CHARGING_CONTROL_CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setContentIntent(mainPendingIntent)
|
||||
.setSmallIcon(R.drawable.ic_charging_control)
|
||||
.setOngoing(true)
|
||||
.addAction(R.drawable.ic_charging_control,
|
||||
mContext.getString(
|
||||
R.string.charging_control_notification_cancel_once),
|
||||
cancelPendingIntent);
|
||||
|
||||
createNotificationChannelIfNeeded();
|
||||
mNotificationManager.notify(CHARGING_CONTROL_NOTIFICATION_ID, notification.build());
|
||||
}
|
||||
|
||||
private void postChargingDoneNotification(Long targetTime) {
|
||||
cancelChargingControlNotification();
|
||||
|
||||
String title = mContext.getString(R.string.charging_control_notification_title);
|
||||
String message;
|
||||
if (targetTime != null) {
|
||||
message = mContext.getString(
|
||||
R.string.charging_control_notification_content_target_reached);
|
||||
} else {
|
||||
message = String.format(
|
||||
mContext.getString(
|
||||
R.string.charging_control_notification_content_limit_reached),
|
||||
mConfigLimit);
|
||||
}
|
||||
|
||||
Intent mainIntent = new Intent(INTENT_PARTS);
|
||||
mainIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
PendingIntent mainPendingIntent = PendingIntent.getActivity(mContext, 0, mainIntent,
|
||||
PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
Notification.Builder notification = new Notification.Builder(mContext,
|
||||
CHARGING_CONTROL_CHANNEL_ID)
|
||||
.setContentTitle(title)
|
||||
.setContentText(message)
|
||||
.setContentIntent(mainPendingIntent)
|
||||
.setSmallIcon(R.drawable.ic_charging_control)
|
||||
.setOngoing(false);
|
||||
|
||||
createNotificationChannelIfNeeded();
|
||||
mNotificationManager.notify(CHARGING_CONTROL_NOTIFICATION_ID, notification.build());
|
||||
}
|
||||
|
||||
private void createNotificationChannelIfNeeded() {
|
||||
String id = CHARGING_CONTROL_CHANNEL_ID;
|
||||
NotificationChannel channel = mNotificationManager.getNotificationChannel(id);
|
||||
if (channel != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String name = mContext.getString(R.string.charging_control_notification_channel);
|
||||
int importance = NotificationManager.IMPORTANCE_LOW;
|
||||
NotificationChannel batteryHealthChannel = new NotificationChannel(id, name,
|
||||
importance);
|
||||
batteryHealthChannel.setBlockable(true);
|
||||
mNotificationManager.createNotificationChannel(batteryHealthChannel);
|
||||
}
|
||||
|
||||
private void cancelChargingControlNotification() {
|
||||
mNotificationManager.cancel(CHARGING_CONTROL_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
/* Notification Broadcast Receiver */
|
||||
private class LineageHealthNotificationBroadcastReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
handleNotificationIntent(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* A representation of start and target time */
|
||||
static final class ChargeTime {
|
||||
private final long mStartTime;
|
||||
private final long mTargetTime;
|
||||
|
||||
ChargeTime(long startTime, long targetTime) {
|
||||
mStartTime = startTime;
|
||||
mTargetTime = targetTime;
|
||||
}
|
||||
|
||||
public long getStartTime() {
|
||||
return mStartTime;
|
||||
}
|
||||
|
||||
public long getTargetTime() {
|
||||
return mTargetTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.lineageos.platform.internal.health;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.ServiceThread;
|
||||
|
||||
import org.lineageos.platform.internal.LineageSystemService;
|
||||
|
||||
import lineageos.app.LineageContextConstants;
|
||||
import lineageos.health.IHealthInterface;
|
||||
import vendor.lineage.health.ChargingControlSupportedMode;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HealthInterfaceService extends LineageSystemService {
|
||||
|
||||
private static final String TAG = "LineageHealth";
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final ServiceThread mHandlerThread;
|
||||
|
||||
private final List<LineageHealthFeature> mFeatures = new ArrayList<LineageHealthFeature>();
|
||||
|
||||
// Health features
|
||||
private ChargingControlController mCCC;
|
||||
|
||||
public HealthInterfaceService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
|
||||
mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_DEFAULT, false);
|
||||
mHandlerThread.start();
|
||||
mHandler = new Handler(mHandlerThread.getLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeatureDeclaration() {
|
||||
return LineageContextConstants.Features.HEALTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCoreService() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (!mContext.getPackageManager().hasSystemFeature(
|
||||
LineageContextConstants.Features.HEALTH)) {
|
||||
Log.wtf(TAG, "Lineage Health service started by system server but feature xml "
|
||||
+ "not declared. Not publishing binder service!");
|
||||
return;
|
||||
}
|
||||
mCCC = new ChargingControlController(mContext, mHandler);
|
||||
if (mCCC.isSupported()) {
|
||||
mFeatures.add(mCCC);
|
||||
}
|
||||
|
||||
if (!mFeatures.isEmpty()) {
|
||||
publishBinderService(LineageContextConstants.LINEAGE_HEALTH_INTERFACE, mService);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (phase != PHASE_BOOT_COMPLETED) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start and update all features
|
||||
for (LineageHealthFeature feature : mFeatures) {
|
||||
feature.start();
|
||||
}
|
||||
}
|
||||
|
||||
/* Service */
|
||||
private final IBinder mService = new IHealthInterface.Stub() {
|
||||
@Override
|
||||
public boolean isChargingControlSupported() {
|
||||
return mCCC.isSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getChargingControlEnabled() {
|
||||
return mCCC.isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChargingControlEnabled(boolean enabled) {
|
||||
return mCCC.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChargingControlMode() {
|
||||
return mCCC.getMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChargingControlMode(int mode) {
|
||||
return mCCC.setMode(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChargingControlStartTime() {
|
||||
return mCCC.getStartTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChargingControlStartTime(int startTime) {
|
||||
return mCCC.setStartTime(startTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChargingControlTargetTime() {
|
||||
return mCCC.getTargetTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChargingControlTargetTime(int targetTime) {
|
||||
return mCCC.setTargetTime(targetTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getChargingControlLimit() {
|
||||
return mCCC.getLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setChargingControlLimit(int limit) {
|
||||
return mCCC.setLimit(limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean resetChargingControl() {
|
||||
return mCCC.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowFineGrainedSettings() {
|
||||
// We allow fine-grained settings if allow toggle and bypass
|
||||
return mCCC.isChargingModeSupported(ChargingControlSupportedMode.TOGGLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||||
mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP, TAG);
|
||||
|
||||
pw.println();
|
||||
pw.println("LineageHealth Service State:");
|
||||
|
||||
for (LineageHealthFeature feature : mFeatures) {
|
||||
feature.dump(pw);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.lineageos.platform.internal.health;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.lineageos.platform.internal.LineageBaseFeature;
|
||||
|
||||
public abstract class LineageHealthFeature extends LineageBaseFeature {
|
||||
protected static final String TAG = "LineageHealth";
|
||||
|
||||
public LineageHealthFeature(Context context, Handler handler) {
|
||||
super(context, handler);
|
||||
}
|
||||
|
||||
public abstract boolean isSupported();
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<!--
|
||||
/**
|
||||
* Copyright (C) 2015 The CyanogenMod Project
|
||||
* 2017-2022 The LineageOS Project
|
||||
* 2017-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.
|
||||
@@ -37,6 +37,8 @@
|
||||
|
||||
<protected-broadcast android:name="lineageos.platform.intent.action.UPDATE_TWILIGHT_STATE" />
|
||||
|
||||
<protected-broadcast android:name="lineageos.platform.intent.action.CHARGING_CONTROL_CANCEL_ONCE" />
|
||||
|
||||
<!-- Allows an application access to the Lineage hardware abstraction framework
|
||||
<p>Not for use by third-party applications. -->
|
||||
<permission android:name="lineageos.permission.HARDWARE_ABSTRACTION_ACCESS"
|
||||
|
||||
27
lineage/res/res/drawable/ic_charging_control.xml
Normal file
27
lineage/res/res/drawable/ic_charging_control.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
Copyright (C) 2018 The Android Open Source 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="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
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="#FF000000"
|
||||
android:pathData="M11.17,18.42v-4.58H9.5l3.33,-6.25v4.58h1.67L11.17,18.42z"/>
|
||||
</vector>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2015 The CyanogenMod Project
|
||||
2017-2022 The LineageOS Project
|
||||
2017-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.
|
||||
@@ -106,6 +106,7 @@
|
||||
<item>org.lineageos.platform.internal.TrustInterfaceService</item>
|
||||
<item>org.lineageos.platform.internal.LineageSettingsService</item>
|
||||
<item>org.lineageos.platform.internal.LineageGlobalActionsService</item>
|
||||
<item>org.lineageos.platform.internal.health.HealthInterfaceService</item>
|
||||
</string-array>
|
||||
|
||||
<!-- The LineageSystemServer class that is invoked from Android's SystemServer -->
|
||||
@@ -306,4 +307,40 @@
|
||||
|
||||
<!-- The list of package IDs that are allowed to skip camera high frame rate checks. -->
|
||||
<string-array name="config_cameraHFRPrivAppList" translatable="false" />
|
||||
|
||||
<!-- Whether charging control should be enabled by default -->
|
||||
<bool name="config_chargingControlEnabled">false</bool>
|
||||
|
||||
<!-- Default charging control mode.
|
||||
This integer should be set to:
|
||||
|
||||
1 - auto - Use the alarm to calculate the time range when to activate charging control
|
||||
2 - custom - Use time range when the device is usually charging for hours
|
||||
3 - limit - Just limit charging -->
|
||||
<integer name="config_defaultChargingControlMode">1</integer>
|
||||
|
||||
<!-- Default time when charging control is activated.
|
||||
Represented as seconds from midnight (e.g. 79200 == 10pm). -->
|
||||
<integer name="config_defaultChargingControlStartTime">79200</integer>
|
||||
|
||||
<!-- Default time when battery will be fully charged.
|
||||
Represented as seconds from midnight (e.g. 21600 == 6am). -->
|
||||
<integer name="config_defaultChargingControlTargetTime">21600</integer>
|
||||
|
||||
<!-- Default charging limit. -->
|
||||
<integer name="config_defaultChargingControlLimit">80</integer>
|
||||
|
||||
<!-- Considering the fact that the system might have an incorrect estimation of the time to
|
||||
full. Set a time margin to make the device fully charged before the target time arrives.
|
||||
|
||||
The unit is minutes and the default value is 30 minutes. If you find that it is not enough
|
||||
to make the device to be fully charged at the target time, increase the value
|
||||
-->
|
||||
<integer name="config_chargingControlTimeMargin">30</integer>
|
||||
|
||||
<!-- For a device that cannot bypass battery when charging stops (that is, the battery current
|
||||
is 0mA when charging stops), the battery will gradually discharge. So we need to make it
|
||||
recharge when the battery level is lower than a threshold. Set this so that the device
|
||||
will be charged between (limit - val) and limit. -->
|
||||
<integer name="config_chargingControlBatteryRechargeMargin">10</integer>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2015 The CyanogenMod Project
|
||||
2017-2022 The LineageOS Project
|
||||
2017-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.
|
||||
@@ -124,4 +124,13 @@
|
||||
<string name="trust_notification_title_onboarding">Discover Trust</string>
|
||||
<string name="trust_notification_content_onboarding">Get to know how to assure your device is safe</string>
|
||||
<string name="trust_notification_action_manage">Manage alerts</string>
|
||||
|
||||
<!-- Health interface -->
|
||||
<string name="charging_control_notification_channel">Charging control</string>
|
||||
<string name="charging_control_notification_title">Charging control</string>
|
||||
<string name="charging_control_notification_cancel_once">Cancel</string>
|
||||
<string name="charging_control_notification_content_limit">Battery will be charged to %1$d%%</string>
|
||||
<string name="charging_control_notification_content_limit_reached">Battery is charged to %1$d%%</string>
|
||||
<string name="charging_control_notification_content_target">Battery will be fully charged at %1$s</string>
|
||||
<string name="charging_control_notification_content_target_reached">Battery is charged</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2015 The CyanogenMod Project
|
||||
2017-2022 The LineageOS Project
|
||||
2017-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.
|
||||
@@ -182,4 +182,21 @@
|
||||
<java-symbol type="array" name="config_cameraAuxPackageAllowList" />
|
||||
<java-symbol type="array" name="config_cameraAuxPackageExcludeList" />
|
||||
<java-symbol type="array" name="config_cameraHFRPrivAppList" />
|
||||
|
||||
<!-- Health interface -->
|
||||
<java-symbol type="bool" name="config_chargingControlEnabled" />
|
||||
<java-symbol type="integer" name="config_defaultChargingControlMode" />
|
||||
<java-symbol type="integer" name="config_defaultChargingControlStartTime" />
|
||||
<java-symbol type="integer" name="config_defaultChargingControlTargetTime" />
|
||||
<java-symbol type="integer" name="config_defaultChargingControlLimit" />
|
||||
<java-symbol type="drawable" name="ic_charging_control" />
|
||||
<java-symbol type="integer" name="config_chargingControlTimeMargin" />
|
||||
<java-symbol type="integer" name="config_chargingControlBatteryRechargeMargin" />
|
||||
<java-symbol type="string" name="charging_control_notification_channel" />
|
||||
<java-symbol type="string" name="charging_control_notification_title" />
|
||||
<java-symbol type="string" name="charging_control_notification_cancel_once" />
|
||||
<java-symbol type="string" name="charging_control_notification_content_limit" />
|
||||
<java-symbol type="string" name="charging_control_notification_content_limit_reached" />
|
||||
<java-symbol type="string" name="charging_control_notification_content_target" />
|
||||
<java-symbol type="string" name="charging_control_notification_content_target_reached" />
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Copyright (C) 2015, The CyanogenMod Project
|
||||
* 2017-2022 The LineageOS Project
|
||||
* 2017-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.
|
||||
@@ -81,6 +81,17 @@ public final class LineageContextConstants {
|
||||
*/
|
||||
public static final String LINEAGE_TRUST_INTERFACE = "lineagetrust";
|
||||
|
||||
/**
|
||||
* Use with {@link android.content.Context#getSystemService} to retrieve a
|
||||
* {@link lineageos.health.HealthInterface} to access the Health interface.
|
||||
*
|
||||
* @see android.content.Context#getSystemService
|
||||
* @see lineageos.health.HealthInterface
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String LINEAGE_HEALTH_INTERFACE = "lineagehealth";
|
||||
|
||||
/**
|
||||
* Update power menu (GlobalActions)
|
||||
*
|
||||
@@ -155,5 +166,13 @@ public final class LineageContextConstants {
|
||||
*/
|
||||
@SdkConstant(SdkConstant.SdkConstantType.FEATURE)
|
||||
public static final String GLOBAL_ACTIONS = "org.lineageos.globalactions";
|
||||
|
||||
/**
|
||||
* Feature for {@link PackageManager#getSystemAvailableFeatures} and
|
||||
* {@link PackageManager#hasSystemFeature}: The device includes the lineage health
|
||||
* service utilized by the lineage sdk and LineageParts.
|
||||
*/
|
||||
@SdkConstant(SdkConstant.SdkConstantType.FEATURE)
|
||||
public static final String HEALTH = "org.lineageos.health";
|
||||
}
|
||||
}
|
||||
|
||||
282
sdk/src/java/lineageos/health/HealthInterface.java
Normal file
282
sdk/src/java/lineageos/health/HealthInterface.java
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 lineageos.health;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import lineageos.app.LineageContextConstants;
|
||||
|
||||
public class HealthInterface {
|
||||
/**
|
||||
* No config set. This value is invalid and does not have any effects
|
||||
*/
|
||||
public static final int MODE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Automatic config
|
||||
*/
|
||||
public static final int MODE_AUTO = 1;
|
||||
|
||||
/**
|
||||
* Manual config mode
|
||||
*/
|
||||
public static final int MODE_MANUAL = 2;
|
||||
|
||||
/**
|
||||
* Limit config mode
|
||||
*/
|
||||
public static final int MODE_LIMIT = 3;
|
||||
|
||||
private static final String TAG = "HealthInterface";
|
||||
private static IHealthInterface sService;
|
||||
private static HealthInterface sInstance;
|
||||
private Context mContext;
|
||||
|
||||
private HealthInterface(Context context) {
|
||||
Context appContext = context.getApplicationContext();
|
||||
mContext = appContext == null ? context : appContext;
|
||||
sService = getService();
|
||||
|
||||
if (context.getPackageManager().hasSystemFeature(
|
||||
LineageContextConstants.Features.HEALTH) && sService == null) {
|
||||
throw new RuntimeException("Unable to get HealthInterfaceService. The service" +
|
||||
" either crashed, was not started, or the interface has been called too early" +
|
||||
" in SystemServer init");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create an instance of the {@link lineageos.health.HealthInterface}
|
||||
*
|
||||
* @param context Used to get the service
|
||||
* @return {@link HealthInterface}
|
||||
*/
|
||||
public static synchronized HealthInterface getInstance(Context context) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new HealthInterface(context);
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/** @hide **/
|
||||
public static IHealthInterface getService() {
|
||||
if (sService != null) {
|
||||
return sService;
|
||||
}
|
||||
IBinder b = ServiceManager.getService(LineageContextConstants.LINEAGE_HEALTH_INTERFACE);
|
||||
sService = IHealthInterface.Stub.asInterface(b);
|
||||
|
||||
if (sService == null) {
|
||||
Log.e(TAG, "null health service, SAD!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return sService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if service is valid
|
||||
*/
|
||||
private boolean checkService() {
|
||||
if (sService == null) {
|
||||
Log.w(TAG, "not connected to LineageHardwareManagerService");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether charging control is supported
|
||||
*
|
||||
* @return true if charging control is supported
|
||||
*/
|
||||
public boolean isChargingControlSupported() {
|
||||
try {
|
||||
return checkService() && sService.isChargingControlSupported();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, e.getLocalizedMessage(), e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charging control enabled status
|
||||
*
|
||||
* @return whether charging control has been enabled
|
||||
*/
|
||||
public boolean getEnabled() {
|
||||
try {
|
||||
return checkService() && sService.getChargingControlEnabled();
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set charging control enable status
|
||||
*
|
||||
* @param enabled whether charging control should be enabled
|
||||
* @return true if the enabled status was successfully set
|
||||
*/
|
||||
public boolean setEnabled(boolean enabled) {
|
||||
try {
|
||||
return checkService() && sService.setChargingControlEnabled(enabled);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current charging control mode
|
||||
*
|
||||
* @return id of the charging control mode
|
||||
*/
|
||||
public int getMode() {
|
||||
try {
|
||||
return checkService() ? sService.getChargingControlMode() : MODE_NONE;
|
||||
} catch (RemoteException e) {
|
||||
return MODE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the new charging control mode
|
||||
*
|
||||
* @param mode the new charging control mode
|
||||
* @return true if the mode was successfully set
|
||||
*/
|
||||
public boolean setMode(int mode) {
|
||||
try {
|
||||
return checkService() && sService.setChargingControlMode(mode);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charging control start time
|
||||
*
|
||||
* @return the seconds of the day of the start time
|
||||
*/
|
||||
public int getStartTime() {
|
||||
try {
|
||||
return checkService() ? sService.getChargingControlStartTime() : 0;
|
||||
} catch (RemoteException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charging control start time
|
||||
*
|
||||
* @param time the seconds of the day of the start time
|
||||
* @return true if the start time was successfully set
|
||||
*/
|
||||
public boolean setStartTime(int time) {
|
||||
try {
|
||||
return checkService() && sService.setChargingControlStartTime(time);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charging control target time
|
||||
*
|
||||
* @return the seconds of the day of the target time
|
||||
*/
|
||||
public int getTargetTime() {
|
||||
try {
|
||||
return checkService() ? sService.getChargingControlTargetTime() : 0;
|
||||
} catch (RemoteException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charging control target time
|
||||
*
|
||||
* @param time the seconds of the day of the target time
|
||||
* @return true if the target time was successfully set
|
||||
*/
|
||||
public boolean setTargetTime(int time) {
|
||||
try {
|
||||
return checkService() && sService.setChargingControlTargetTime(time);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the charging control limit
|
||||
*
|
||||
* @return the charging control limit
|
||||
*/
|
||||
public int getLimit() {
|
||||
try {
|
||||
return checkService() ? sService.getChargingControlLimit() : 100;
|
||||
} catch (RemoteException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the charging control limit
|
||||
*
|
||||
* @param limit the charging control limit
|
||||
* @return true if the limit was successfully set
|
||||
*/
|
||||
public boolean setLimit(int limit) {
|
||||
try {
|
||||
return checkService() && sService.setChargingControlLimit(limit);
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the charging control setting to default
|
||||
*
|
||||
* @return true if the setting was successfully reset
|
||||
*/
|
||||
public boolean reset() {
|
||||
try {
|
||||
return checkService() && sService.resetChargingControl();
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the device's battery control bypasses battery
|
||||
*
|
||||
* @return true if the charging control bypasses battery
|
||||
*/
|
||||
public boolean allowFineGrainedSettings() {
|
||||
try {
|
||||
return checkService() && sService.allowFineGrainedSettings();
|
||||
} catch (RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
sdk/src/java/lineageos/health/IHealthInterface.aidl
Normal file
40
sdk/src/java/lineageos/health/IHealthInterface.aidl
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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 lineageos.health;
|
||||
|
||||
/** @hide */
|
||||
interface IHealthInterface {
|
||||
boolean isChargingControlSupported();
|
||||
|
||||
boolean getChargingControlEnabled();
|
||||
boolean setChargingControlEnabled(boolean enabled);
|
||||
|
||||
int getChargingControlMode();
|
||||
boolean setChargingControlMode(int mode);
|
||||
|
||||
int getChargingControlStartTime();
|
||||
boolean setChargingControlStartTime(int time);
|
||||
|
||||
int getChargingControlTargetTime();
|
||||
boolean setChargingControlTargetTime(int time);
|
||||
|
||||
int getChargingControlLimit();
|
||||
boolean setChargingControlLimit(int limit);
|
||||
|
||||
boolean resetChargingControl();
|
||||
boolean allowFineGrainedSettings();
|
||||
}
|
||||
Reference in New Issue
Block a user