Android 9.0 Battery Settings
- 1. 主界面显示
- 1.1 battery_header
- 2.2 battery_tip
- 2.3 battery_saver
- 2.3.1 auto_battery_saver
- 2.3.2 battery_saver_seek_bar
- 2.3.3 battery_saver Button
- 2.3.3.1 BatterySaverButtonPreferenceController.java
- 2.3.3.2 BatterySaverUtils.java#setPowerSaveMode
- 2.3.3.3 PowerManager.java
- 2.3.3.4 IPowerManager.aidl
- 2.3.3.5 PowerManagerService.java # BinderService
- 2.3.3.6 BatterySaverStateMachine.java # setBatterySaverEnabledManually
- 2.3.3.7 BatterySaverController.java # enableBatterySaver
- 2.3.4 battery_saver_footer
- 2.4 smart_battery_manager
- 2.5 battery_percentage
- 2.6 last_full_charge
- 2.7 screen_usage
1. 主界面显示
PowerUsageSummary.java 布局文件是 power_usage_summary.xml
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="power_usage_summary_screen"
android:title="@string/power_usage_summary_title"
settings:keywords="@string/keywords_battery">
<com.android.settings.applications.LayoutPreference
android:key="battery_header"
android:selectable="false"
android:layout="@layout/battery_header" />
<PreferenceCategory
android:key="battery_tip"
android:layout="@layout/preference_category_no_title" />
<Preference
android:fragment="com.android.settings.fuelgauge.batterysaver.BatterySaverSettings"
android:key="battery_saver_summary"
android:title="@string/battery_saver"
settings:controller="com.android.settings.fuelgauge.BatterySaverController"
settings:platform_slice="true"
settings:allowDividerAbove="true" />
<Preference
android:fragment="com.android.settings.fuelgauge.SmartBatterySettings"
android:key="smart_battery_manager"
android:title="@string/smart_battery_manager_title"
settings:controller="com.android.settings.fuelgauge.batterytip.BatteryManagerPreferenceController" />
<SwitchPreference
android:key="battery_percentage"
android:title="@string/battery_percentage"
android:summary="@string/battery_percentage_description" />
<com.android.settings.fuelgauge.PowerGaugePreference
android:key="last_full_charge"
android:title="@string/battery_last_full_charge"
android:selectable="false"
settings:allowDividerAbove="true" />
<com.android.settings.fuelgauge.PowerGaugePreference
android:key="screen_usage"
android:title="@string/device_screen_usage"
android:selectable="false" />
</PreferenceScreen>
1.1 battery_header
布局文件是 battery_header.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/battery_entity_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="horizontal"
android:paddingTop="24dp"
android:paddingBottom="24dp"
style="@style/EntityHeader">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="56dp"
android:orientation="vertical">
<TextView
android:id="@+id/battery_percent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:fontFamily="@*android:string/config_headlineFontFamily"
android:textAppearance="@android:style/TextAppearance.Material.Display1"/>
<TextView
android:id="@+id/summary1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:minLines="2"
android:textAppearance="@android:style/TextAppearance.Material.Small"/>
<TextView
android:id="@+id/summary2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@android:style/TextAppearance.Material.Small"/>
</LinearLayout>
<com.android.settings.fuelgauge.BatteryMeterView
android:id="@+id/battery_header_icon"
android:layout_width="@dimen/battery_meter_width"
android:layout_height="@dimen/battery_meter_height"
android:layout_marginEnd="16dp"/>
</LinearLayout>
对应 id为 battery_percent 的TextView
对应 id为 summary1 的TextView
对应 id为 summary2 的TextView
对应id为 battery_header_icon 的 BatteryMeterView,电池图标是由BatteryMeterView绘制而成
在 PowerUsageSummary.java 通过 key 获取到 对应的 LayoutPreference ,再使用mBatteryLayoutPref.findViewById 获取对应的控件,设置初始化参数。此时设置的是旧的值,从battery info获取。
protected void updateViews(List<BatteryInfo> batteryInfos) {
final BatteryMeterView batteryView = mBatteryLayoutPref
.findViewById(R.id.battery_header_icon);
final TextView percentRemaining =
mBatteryLayoutPref.findViewById(R.id.battery_percent);
final TextView summary1 = mBatteryLayoutPref.findViewById(R.id.summary1);
final TextView summary2 = mBatteryLayoutPref.findViewById(R.id.summary2);
BatteryInfo oldInfo = batteryInfos.get(0);
BatteryInfo newInfo = batteryInfos.get(1);
percentRemaining.setText(Utils.formatPercentage(oldInfo.batteryLevel));
// set the text to the old estimate (copied from battery info). Note that this
// can sometimes say 0 time remaining because battery stats requires the phone
// be unplugged for a period of time before being willing ot make an estimate.
summary1.setText(mPowerFeatureProvider.getOldEstimateDebugString(
Formatter.formatShortElapsedTime(getContext(),
PowerUtil.convertUsToMs(oldInfo.remainingTimeUs))));
// for this one we can just set the string directly
summary2.setText(mPowerFeatureProvider.getEnhancedEstimateDebugString(
Formatter.formatShortElapsedTime(getContext(),
PowerUtil.convertUsToMs(newInfo.remainingTimeUs))));
batteryView.setBatteryLevel(oldInfo.batteryLevel);
batteryView.setCharging(!oldInfo.discharging);
}
使用Loader之后,只要数据源发生变化,就会自动调用onLoadFinished(),数据源是
LoaderManager.LoaderCallbacks<List<BatteryInfo>> mBatteryInfoDebugLoaderCallbacks =
new LoaderCallbacks<List<BatteryInfo>>() {
@Override
//创建新的Loader,i为LoaderID,如果已经有相同ID的Loader就会复用该Loader,而不会重新创建。 args为初始化时传递的参数。该方法开始异步查询,并返回一个泛型类,如果是查询数据库可以返回一个CursorLoader
public Loader<List<BatteryInfo>> onCreateLoader(int i, Bundle bundle) {
return new DebugEstimatesLoader(getContext(), mStatsHelper);
}
@Override
//异步查询结束的会调用这个方法,并返回查询结果 data
public void onLoadFinished(Loader<List<BatteryInfo>> loader,
List<BatteryInfo> batteryInfos) {
updateViews(batteryInfos);
}
@Override
public void onLoaderReset(Loader<List<BatteryInfo>> loader) {
}
};
battery_header 绑定的Controller是 BatteryHeaderPreferenceController.java
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
...
mBatteryHeaderPreferenceController = new BatteryHeaderPreferenceController(
context, activity, this /* host */, lifecycle);
controllers.add(mBatteryHeaderPreferenceController);
在 PowerUsageSummary.java#oncreate() & refreshUi() 方法中都去更新了这几个view的状态
PowerUsageSummary.java # mBatteryInfoLoaderCallbacks 调用
public void updateHeaderPreference(BatteryInfo info) {
mBatteryPercentText.setText(Utils.formatPercentage(info.batteryLevel));
if (info.remainingLabel == null) {
mSummary1.setText(info.statusLabel);
} else {
mSummary1.setText(info.remainingLabel);
}
// Clear this just to be sure we don't get UI jank on re-entering this view from another
// activity.
mSummary2.setText("");
mBatteryMeterView.setBatteryLevel(info.batteryLevel);
mBatteryMeterView.setCharging(!info.discharging);
}
public void quickUpdateHeaderPreference() {
Intent batteryBroadcast = mContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));//注册广播获取电池信息
final int batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
final boolean discharging =
batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0;
// Set battery level and charging status
mBatteryMeterView.setBatteryLevel(batteryLevel);
mBatteryMeterView.setCharging(!discharging);
mBatteryPercentText.setText(Utils.formatPercentage(batteryLevel));
// clear all the summaries
mSummary1.setText("");
mSummary2.setText("");
}
2.2 battery_tip
在 PowerUsageSummary.java 通过key,将 BatteryTipPreferenceController 加入到 controller 集合中,用来控制显示。
在displayPreference 方法中,加入了SummaryTip这个preference。
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPrefContext = screen.getContext();
mPreferenceGroup = (PreferenceGroup) screen.findPreference(getPreferenceKey());
// Add summary tip in advance to avoid UI flakiness
final SummaryTip summaryTip = new SummaryTip(BatteryTip.StateType.NEW,
Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN);
mPreferenceGroup.addPreference(summaryTip.buildPreference(mPrefContext));
}
在 PowerUsageSummary# LoaderManager.LoaderCallbacks中,更新SummaryTip的状态
mBatteryTipPreferenceController.updateBatteryTips(data);
BatteryTipPreferenceController.java
public void updateBatteryTips(List<BatteryTip> batteryTips) {
if (batteryTips == null) {
return;
}
if (mBatteryTips == null) {
mBatteryTips = batteryTips;
} else {
// mBatteryTips and batteryTips always have the same length and same sequence.
for (int i = 0, size = batteryTips.size(); i < size; i++) {
mBatteryTips.get(i).updateState(batteryTips.get(i));
}
}
mPreferenceGroup.removeAll();
for (int i = 0, size = batteryTips.size(); i < size; i++) {
final BatteryTip batteryTip = mBatteryTips.get(i);
if (batteryTip.getState() != BatteryTip.StateType.INVISIBLE) {
final Preference preference = batteryTip.buildPreference(mPrefContext);
mBatteryTipMap.put(preference.getKey(), batteryTip);
mPreferenceGroup.addPreference(preference);
batteryTip.log(mContext, mMetricsFeatureProvider);
mNeedUpdate = batteryTip.needUpdate();
break;
}
}
}
还对SummaryTip 添加了点击事件,用来弹出dialog
public boolean handlePreferenceTreeClick(Preference preference) {
final BatteryTip batteryTip = mBatteryTipMap.get(preference.getKey());
if (batteryTip != null) {
if (batteryTip.shouldShowDialog()) {//显示dialog
BatteryTipDialogFragment dialogFragment = BatteryTipDialogFragment.newInstance(
batteryTip, mFragment.getMetricsCategory());
dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
dialogFragment.show(mFragment.getFragmentManager(), TAG);
} else {
final BatteryTipAction action = BatteryTipUtils.getActionForBatteryTip(batteryTip,
mSettingsActivity, mFragment);
if (action != null) {
action.handlePositiveAction(mFragment.getMetricsCategory());
}
if (mBatteryTipListener != null) {
mBatteryTipListener.onBatteryTipHandled(batteryTip);
}
}
return true;
}
return super.handlePreferenceTreeClick(preference);
}
2.3 battery_saver
省电模式主要做什么?
DisplayPowerController -> 屏幕亮度减半
VibratorService -> 关闭触摸震动和来电震动
NetworkPolicyManagerService -> 限制 Doze 非白名单应用联网(WiFi与数据流量)使用
WindowManagerService -> 关闭动画
PowerManagerService -> 省电模式-CPU降频
UiModeManagerService -> 开启暗色主题模式(Q平台版本新增)
GnssLocationProvider -> 灭屏后开启GPS待机省电模式
SoundTriggerHelper -> 拦截语音交互功能
DeviceStateMonitor -> 设置Modem为省电模式
PowerUI -> 取消低电量通知提醒
<Preference
<!--绑定的fragment-->
android:fragment="com.android.settings.fuelgauge.batterysaver.BatterySaverSettings"
android:key="battery_saver_summary"
android:title="@string/battery_saver"
<!--绑定的controller-->
settings:controller="com.android.settings.fuelgauge.BatterySaverController"
settings:platform_slice="true"
settings:allowDividerAbove="true" />
BatterySaverController #onStart() 控制 summary的显示,
- 注册了一个ContentObserveryong用来监测LOW_POWER_MODE_TRIGGER_LEVEL 的改变,
- 利用mBatteryStateChangeReceiver来接收
PowerManager.ACTION_POWER_SAVE_MODE_CHANGING
Intent.ACTION_BATTERY_CHANGED
的广播状态。 - 最后更新summary
@Override
public void onStart() {
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL)
, true, mObserver);
mBatteryStateChangeReceiver.setListening(true);
updateSummary();
}
...
private final ContentObserver mObserver = new ContentObserver(
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
updateSummary();
}
};
updateSummary() 会调用 getSummary()
@Override
public CharSequence getSummary() {
final boolean isPowerSaveOn = mPowerManager.isPowerSaveMode();
final int percent = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
if (isPowerSaveOn) {
return mContext.getString(R.string.battery_saver_on_summary);
} else if (percent != 0) {
return mContext.getString(R.string.battery_saver_off_scheduled_summary,
Utils.formatPercentage(percent));
} else {
return mContext.getString(R.string.battery_saver_off_summary);
}
}
利用mPowerManager 获取是否打开了savemode,还有获取 LOW_POWER_MODE_TRIGGER_LEVEL的值,来确定最终的summary字符串是什么。
isPowerSaveMode() 获取流程
进入设置项
按钮打开和关闭的显示页面
布局文件如下:
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:title="@string/battery_saver"
android:key="battery_saver_page">
<!-- Turn on automatically -->
<SwitchPreference
android:key="auto_battery_saver"
android:title="@string/battery_saver_auto_title"
settings:controller="com.android.settings.fuelgauge.batterysaver.AutoBatterySaverPreferenceController" />
<com.android.settings.widget.SeekBarPreference
android:key="battery_saver_seek_bar"
android:title="@string/battery_saver_seekbar_title_placeholder"
android:max="75"
android:min="5" />
<com.android.settings.widget.TwoStateButtonPreference
android:key="battery_saver"
android:title="@string/battery_saver"
android:selectable="false"
settings:textOn="@string/battery_saver_button_turn_on"
settings:textOff="@string/battery_saver_button_turn_off"
settings:platform_slice="true"
settings:controller="com.android.settings.fuelgauge.batterysaver.BatterySaverButtonPreferenceController" />
<PreferenceCategory
android:key="battery_saver_footer">
<com.android.settingslib.widget.FooterPreference
android:key="battery_saver_footer_preference"
android:title="@*android:string/battery_saver_description"
android:selectable="false" />
</PreferenceCategory>
</PreferenceScreen>
2.3.1 auto_battery_saver
布局文件 battery_saver_settings.xml
<SwitchPreference
android:key="auto_battery_saver"
android:title="@string/battery_saver_auto_title"
settings:controller="com.android.settings.fuelgauge.batterysaver.AutoBatterySaverPreferenceController" />
AutoBatterySaverPreferenceController 控制控件的状态显示,获取状态值,确定按钮是打开还是关闭
...
mDefaultTriggerLevelForOn = mContext.getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);//15
...
@Override
public boolean isChecked() {
return Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, DEFAULT_TRIGGER_LEVEL) != 0;
}
@Override
public boolean setChecked(boolean isChecked) {
BatterySaverUtils.setAutoBatterySaverTriggerLevel(mContext,
isChecked ? mDefaultTriggerLevelForOn : 0);
return true;
}
...
打开自动开启:BatterySaverUtils.setAutoBatterySaverTriggerLevel,更新 SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION
LOW_POWER_MODE_TRIGGER_LEVEL
的值
**
* Don't show the automatic battery suggestion notification in the future.
*/
public static void suppressAutoBatterySaver(Context context) {
Secure.putInt(context.getContentResolver(),
Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 1);
}
/**
* Set the automatic battery saver trigger level to {@code level}.
*/
public static void setAutoBatterySaverTriggerLevel(Context context, int level) {
if (level > 0) {
suppressAutoBatterySaver(context);
}
Global.putInt(context.getContentResolver(), Global.LOW_POWER_MODE_TRIGGER_LEVEL, level);
}
2.3.2 battery_saver_seek_bar
<com.android.settings.widget.SeekBarPreference
android:key="battery_saver_seek_bar"
android:title="@string/battery_saver_seekbar_title_placeholder"
android:max="75"
android:min="5" />
在 BatterySaverSettings.java 中加入了控制它的controller : AutoBatterySeekBarPreferenceController.java
/**
* Observer that listens to change from {@link Settings.Global#LOW_POWER_MODE_TRIGGER_LEVEL}
*/
private final class AutoBatterySaverSettingObserver extends ContentObserver {
private final Uri mUri = Settings.Global.getUriFor(
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL);
private final ContentResolver mContentResolver;
public AutoBatterySaverSettingObserver(Handler handler) {
super(handler);
mContentResolver = mContext.getContentResolver();
}
public void registerContentObserver() {
mContentResolver.registerContentObserver(mUri, false, this);
}
public void unRegisterContentObserver() {
mContentResolver.unregisterContentObserver(this);
}
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
if (mUri.equals(uri)) {
updatePreference(mPreference);
}
}
}
使用了ContentObserver来监测 LOW_POWER_MODE_TRIGGER_LEVEL 值的变化,如果是0,就不显示该设置项,如果是其他的值,就正常显示和调节进度值。由于 auto_battery_saver 的开关在关闭时,会设置 LOW_POWER_MODE_TRIGGER_LEVEL 的值为0,所以 battery_saver_seek_bar 会被隐藏。
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
final int progress = (int) newValue;
Settings.Global.putInt(mContext.getContentResolver(),
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, progress);//根据滑动设置seek bar的值
return true;
}
@VisibleForTesting
void updatePreference(Preference preference) {
final ContentResolver contentResolver = mContext.getContentResolver();
// Override the max value with LOW_POWER_MODE_TRIGGER_LEVEL_MAX, if set.
final int maxLevel = Settings.Global.getInt(contentResolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL_MAX, 0);
if (maxLevel > 0) {
if (!(preference instanceof SeekBarPreference)) {
Log.e(TAG, "Unexpected preference class: " + preference.getClass());
} else {
final SeekBarPreference seekBarPreference = (SeekBarPreference) preference;
if (maxLevel < seekBarPreference.getMin()) {
Log.e(TAG, "LOW_POWER_MODE_TRIGGER_LEVEL_MAX too low; ignored.");
} else {
seekBarPreference.setMax(maxLevel);
}
}
}
// Set the current value.
final int level = Settings.Global.getInt(contentResolver,
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL,
AutoBatterySaverPreferenceController.DEFAULT_TRIGGER_LEVEL);
if (level == 0) {
preference.setVisible(false);//隐藏seek bar
} else {
preference.setVisible(true);//显示seek bar
preference.setTitle(mContext.getString(R.string.battery_saver_seekbar_title,
Utils.formatPercentage(level)));
SeekBarPreference seekBarPreference = (SeekBarPreference) preference;
seekBarPreference.setProgress(level);
seekBarPreference.setSeekBarContentDescription(
mContext.getString(R.string.battery_saver_turn_on_automatically_title));
}
}
2.3.3 battery_saver Button
使用的是 TwoStateButtonPreference,在styles_preference.xml 定义了布局和样式
<style name="TwoStateButtonPreference" parent="Preference.SettingsBase">
<item name="android:layout">@layout/two_state_button</item>
</style>
<com.android.settings.widget.TwoStateButtonPreference
android:key="battery_saver"
android:title="@string/battery_saver"
android:selectable="false"
settings:textOn="@string/battery_saver_button_turn_on"
settings:textOff="@string/battery_saver_button_turn_off"
settings:platform_slice="true"
settings:controller="com.android.settings.fuelgauge.batterysaver.BatterySaverButtonPreferenceController" />
控制这个控件的是 BatterySaverButtonPreferenceController.java
在构造方法里面 获取了PowerManager 和 BatterySaverReceiver 并且实现了 BatterySaverListener 的onBatteryChanged 方法。
public BatterySaverButtonPreferenceController(Context context, String key) {
super(context, key);
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mBatterySaverReceiver = new BatterySaverReceiver(context);
mBatterySaverReceiver.setBatterySaverListener(this);
}
...
@Override
public void onBatteryChanged(boolean pluggedIn) {
if (mPreference != null) {
//置灰
mPreference.setButtonEnabled(!pluggedIn);//监测电源插入,数据来自atterySaverListener
}
}
在BatterySaverReceiver.java 中,实现了
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Received " + intent.getAction());
String action = intent.getAction();
if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(action)) {
if (mBatterySaverListener != null) {
mBatterySaverListener.onPowerSaveModeChanged();
}
} else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
// disable BSM switch if phone is plugged in
if (mBatterySaverListener != null) {
final boolean pluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
mBatterySaverListener.onBatteryChanged(pluggedIn);//实时监测电源插入
}
}
}
在不插入电源的时候,加载页面会初始化按钮状态 使用 updateState方法
@Override
public boolean isChecked() {
return mPowerManager.isPowerSaveMode();//调用流程和前面的 2.3 一样
}
...
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (mPreference != null) {
mPreference.setChecked(isChecked());
}
}
打开/关闭 battery saver按钮
代码调用流程:
2.3.3.1 BatterySaverButtonPreferenceController.java
使用 setChecked 方法
@Override
public boolean setChecked(boolean stateOn) {
// This screen already shows a warning, so we don't need another warning.
return BatterySaverUtils.setPowerSaveMode(mContext, stateOn,
false /* needFirstTimeWarning */);
}
2.3.3.2 BatterySaverUtils.java#setPowerSaveMode
public static synchronized boolean setPowerSaveMode(Context context,
boolean enable, boolean needFirstTimeWarning) {
if (DEBUG) {
Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
}
final ContentResolver cr = context.getContentResolver();
//不进入 这个 if
if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context)) {
return false;
}
if (enable && !needFirstTimeWarning) {
setBatterySaverConfirmationAcknowledged(context);
}
if (context.getSystemService(PowerManager.class).setPowerSaveMode(enable)) {
if (enable) {
final int count =
Secure.getInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 0) + 1;
Secure.putInt(cr, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, count);
final Parameters parameters = new Parameters(context);
if ((count >= parameters.startNth)
&& (count <= parameters.endNth)
&& Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0
&& Secure.getInt(cr,
Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) {
showAutoBatterySaverSuggestion(context);
}
}
return true;
}
return false;
}
...
private static void setBatterySaverConfirmationAcknowledged(Context context) {
// 不显示“首次节省电量警告”对话框
Secure.putInt(context.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 1);
}
2.3.3.3 PowerManager.java
public boolean setPowerSaveMode(boolean mode) {
try {
return mService.setPowerSaveMode(mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
2.3.3.4 IPowerManager.aidl
boolean setPowerSaveMode(boolean mode);
2.3.3.5 PowerManagerService.java # BinderService
@Override // Binder call
public boolean setPowerSaveMode(boolean enabled) {
//检查权限
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
final long ident = Binder.clearCallingIdentity();
try {
return setLowPowerModeInternal(enabled);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
...
private boolean setLowPowerModeInternal(boolean enabled) {
synchronized (mLock) {
if (DEBUG) {
Slog.d(TAG, "setLowPowerModeInternal " + enabled + " mIsPowered=" + mIsPowered);
}
if (mIsPowered) {// 是否在充电
return false;
}
mBatterySaverStateMachine.setBatterySaverEnabledManually(enabled);
return true;
}
}
2.3.3.6 BatterySaverStateMachine.java # setBatterySaverEnabledManually
public void setBatterySaverEnabledManually(boolean enabled) {
if (DEBUG) {
Slog.d(TAG, "setBatterySaverEnabledManually: enabled=" + enabled);
}
synchronized (mLock) {
enableBatterySaverLocked(/*enable=*/ enabled, /*manual=*/ true,
(enabled ? BatterySaverController.REASON_MANUAL_ON //2
: BatterySaverController.REASON_MANUAL_OFF),
(enabled ? "Manual ON" : "Manual OFF"));
}
}
...
private void enableBatterySaverLocked(boolean enable, boolean manual, int intReason,
String strReason) {
if (DEBUG) {
Slog.d(TAG, "enableBatterySaver: enable=" + enable + " manual=" + manual
+ " reason=" + strReason + "(" + intReason + ")");
}
//battery saver 是否启用
final boolean wasEnabled = mBatterySaverController.isEnabled();
if (wasEnabled == enable) {//已经启用
if (DEBUG) {
Slog.d(TAG, "Already " + (enable ? "enabled" : "disabled"));
}
return;
}
if (enable && mIsPowered) {//启用battery saver 并且在充电
if (DEBUG) Slog.d(TAG, "Can't enable: isPowered");
return;
}
mLastChangedIntReason = intReason;
mLastChangedStrReason = strReason;
if (manual) {//true
if (enable) {
// 手动禁用了BS
updateSnoozingLocked(false, "Manual snooze OFF");
} else {
// When battery saver is disabled manually (while battery saver is enabled)
// when the battery level is low, we "snooze" BS -- i.e. disable auto battery saver.
// We resume auto-BS once the battery level is not low, or the device is plugged in.
// 当电池电量低的时候,手动禁用电池节能器(当电池节能器启用时),我们“打盹”BS -即禁用自动电池节能器。一旦电池电量不低,或者设备被插入,我们就恢复自动- bs。
if (isBatterySaverEnabled() && mIsBatteryLevelLow) {
updateSnoozingLocked(true, "Manual snooze");
}
}
}
mSettingBatterySaverEnabled = enable;
// 启用低电量模式 (battery saver)
putGlobalSetting(Global.LOW_POWER_MODE, enable ? 1 : 0);
if (manual) {
// 更新状态值
mSettingBatterySaverEnabledSticky = enable;
// 如果是1,当设备从充电器上拔出或重启后,电池节省器({@link #LOW_POWER_MODE})将被重新激活。
putGlobalSetting(Global.LOW_POWER_MODE_STICKY, enable ? 1 : 0);
}
mBatterySaverController.enableBatterySaver(enable, intReason//2);
if (DEBUG) {
Slog.d(TAG, "Battery saver: Enabled=" + enable
+ " manual=" + manual
+ " reason=" + strReason + "(" + intReason + ")");
}
}
2.3.3.7 BatterySaverController.java # enableBatterySaver
public void enableBatterySaver(boolean enable, int reason) {
synchronized (mLock) {
if (mEnabled == enable) {
return;
}
mEnabled = enable;
mHandler.postStateChanged(/*sendBroadcast=*/ true, reason);
}
}
...
private class MyHandler extends Handler {
private static final int MSG_STATE_CHANGED = 1;
private static final int ARG_DONT_SEND_BROADCAST = 0;
private static final int ARG_SEND_BROADCAST = 1;
private static final int MSG_SYSTEM_READY = 2;
...
public void postStateChanged(boolean sendBroadcast, int reason) {
obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget();
...
@Override
public void dispatchMessage(Message msg) {
switch (msg.what) {
case MSG_STATE_CHANGED://获取到 message
handleBatterySaverStateChanged(
msg.arg1 == ARG_SEND_BROADCAST,
msg.arg2);
break;
case MSG_SYSTEM_READY:
for (Plugin p : mPlugins) {
p.onSystemReady(BatterySaverController.this);
}
break;
}
}
}
void handleBatterySaverStateChanged(boolean sendBroadcast, int reason) {
final LowPowerModeListener[] listeners;
final boolean enabled;
final boolean isInteractive = getPowerManager().isInteractive();
final ArrayMap<String, String> fileValues;
synchronized (mLock) {
EventLogTags.writeBatterySaverMode(
mPreviouslyEnabled ? 1 : 0, // Previously off or on.
mEnabled ? 1 : 0, // Now off or on.
isInteractive ? 1 : 0, // Device interactive state.
mEnabled ? mBatterySaverPolicy.toEventLogString() : "",
reason);
mPreviouslyEnabled = mEnabled;
listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
enabled = mEnabled;
mIsInteractive = isInteractive;
if (enabled) {
fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
} else {
fileValues = null;
}
}
final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
if (pmi != null) {
// CPU 调低频
pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
}
updateBatterySavingStats();
if (ArrayUtils.isEmpty(fileValues)) {
mFileUpdater.restoreDefault();
} else {
mFileUpdater.writeFiles(fileValues);
}
for (Plugin p : mPlugins) {
p.onBatterySaverChanged(this);
}
if (sendBroadcast) {
if (DEBUG) {
Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
}
// Send the broadcasts and notify the listeners. We only do this when the battery saver
// mode changes, but not when only the screen state changes.
// 省电模式变化广播 1
// 普通 APP 使用动态广播
Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
.putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
// 省电模式变化广播 2
// 普通 APP 可监听
// 需要重点查看接受这个广播的系统服务做了什么
intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
// 省电模式变化广播 3
// 需要注册 Manifest.permission.DEVICE_POWER 权限且需要系统签名才能收到
// Send internal version that requires signature permission.
intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
Manifest.permission.DEVICE_POWER);
// 系统其它服务,主要注册监听 LowPowerModeListener 变化进行省电模式的开启
for (LowPowerModeListener listener : listeners) {
final PowerSaveState result =
mBatterySaverPolicy.getBatterySaverPolicy(
listener.getServiceType(), enabled);
// 通知所有注册 LowPowerModeListener 的服务会发生改变
listener.onLowPowerModeChanged(result);
}
}
}
2.3.4 battery_saver_footer
<PreferenceCategory
android:key="battery_saver_footer">
<com.android.settingslib.widget.FooterPreference
android:key="battery_saver_footer_preference"
android:title="@*android:string/battery_saver_description"
android:selectable="false" />
</PreferenceCategory>
2.4 smart_battery_manager
<Preference
android:fragment="com.android.settings.fuelgauge.SmartBatterySettings"
android:key="smart_battery_manager"
android:title="@string/smart_battery_manager_title"
settings:controller="com.android.settings.fuelgauge.batterytip.BatteryManagerPreferenceController" />
@Override
public void updateState(Preference preference) {
super.updateState(preference);
// 获取受限 app 列表
final int num = BatteryTipUtils.getRestrictedAppsList(mAppOpsManager, mUserManager).size();
final String setting = mPowerUsageFeatureProvider.isSmartBatterySupported()
? Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED
: Settings.Global.APP_AUTO_RESTRICTION_ENABLED;
// 获取开关状态
final boolean featureOn =
Settings.Global.getInt(mContext.getContentResolver(), setting, ON) == ON;
//更新summary
updateSummary(preference, featureOn, num);
}
@VisibleForTesting
void updateSummary(Preference preference, boolean featureOn, int num) {
if (num > 0) {
preference.setSummary(mContext.getResources().getQuantityString(
R.plurals.battery_manager_app_restricted, num, num));
} else if (featureOn) {
preference.setSummary(R.string.battery_manager_on);
} else {
preference.setSummary(R.string.battery_manager_off);
}
}
2.5 battery_percentage
<SwitchPreference
android:key="battery_percentage"
android:title="@string/battery_percentage"
android:summary="@string/battery_percentage_description" />
BatteryPercentagePreferenceController.java
@Override
public boolean isAvailable() {//该项是否显示
return mContext.getResources()
.getBoolean(R.bool.config_battery_percentage_setting_available);
}
@Override
public void updateState(Preference preference) {
int setting = Settings.System.getInt(mContext.getContentResolver(),
SHOW_BATTERY_PERCENT, 0);//获取状态值
((SwitchPreference) preference).setChecked(setting == 1);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean showPercentage = (Boolean) newValue;
Settings.System.putInt(mContext.getContentResolver(), SHOW_BATTERY_PERCENT,
showPercentage ? 1 : 0);//写入属性值
return true;
}
在 frameworks/base/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java 中利用ContentObserver 区监测 SHOW_BATTERY_PERCENT 的改变,进而显示或隐藏电量百分比
getContext().getContentResolver().registerContentObserver(
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, newUserId);
2.6 last_full_charge
<com.android.settings.fuelgauge.PowerGaugePreference
android:key="last_full_charge"
android:title="@string/battery_last_full_charge"
android:selectable="false"
settings:allowDividerAbove="true" />
代码调用关系
PowerUsageSummary.java
-> onCreate()
-> restartBatteryInfoLoader()
getLoaderManager().restartLoader(BATTERY_INFO_LOADER, Bundle.EMPTY,
mBatteryInfoLoaderCallbacks);
->mBatteryInfoLoaderCallbacks
->updateLastFullChargePreference()
@VisibleForTesting
void updateLastFullChargePreference() {
if (mBatteryInfo != null && mBatteryInfo.averageTimeToDischarge//获取平均放电时间
!= Estimate.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN) {
// 电池充满电后的大致使用时长
mLastFullChargePref.setTitle(R.string.battery_full_charge_last);
mLastFullChargePref.setSubtitle(
StringUtil.formatElapsedTime(getContext(), mBatteryInfo.averageTimeToDischarge,
false /* withSeconds */));
} else {
final long lastFullChargeTime = mBatteryUtils.calculateLastFullChargeTime(mStatsHelper,
System.currentTimeMillis());
mLastFullChargePref.setTitle(R.string.battery_last_full_charge);
mLastFullChargePref.setSubtitle(
StringUtil.formatRelativeTime(getContext(), lastFullChargeTime,
false /* withSeconds */));
}
}
2.7 screen_usage
<com.android.settings.fuelgauge.PowerGaugePreference
android:key="screen_usage"
android:title="@string/device_screen_usage"
android:selectable="false" />
...
mScreenUsagePref.setSubtitle(StringUtil.formatElapsedTime(getContext(),
mBatteryUtils.calculateScreenUsageTime(mStatsHelper), false));
调用 BatteryUtils的calculateScreenUsageTime方法,来获取 BatterySipper
public long calculateScreenUsageTime(BatteryStatsHelper batteryStatsHelper) {
final BatterySipper sipper = findBatterySipperByType(
batteryStatsHelper.getUsageList(), BatterySipper.DrainType.SCREEN);
return sipper != null ? sipper.usageTimeMs : 0;
}
...
public BatterySipper findBatterySipperByType(List<BatterySipper> usageList,
BatterySipper.DrainType type) {
for (int i = 0, size = usageList.size(); i < size; i++) {
final BatterySipper sipper = usageList.get(i);
if (sipper.drainType == type) {
return sipper;
}
}
return null;
}