现象: 连续插拔充电器,出现充电提示音消失,只能重启
插上充电器,PowerManagerService就会调用updatePowerStateLocked()方法来更新整个电源状态的改变,并进行重新计算。当电源状态发生改变时,如亮灭屏、电池状态改变、暗屏…都会调用该方法,在该方法中调用了其他同级方法进行更新。
源码路径: frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void updatePowerStateLocked() {
if (!mSystemReady || mDirty == 0) {
return;
}
if (!Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
}
Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
try {
// Phase 0: Basic state updates.
updateIsPoweredLocked(mDirty);
updateStayOnLocked(mDirty);
updateScreenBrightnessBoostLocked(mDirty);
// Phase 1: Update wakefulness.
// Loop because the wake lock and user activity computations are influenced
// by changes in wakefulness.
final long now = mClock.uptimeMillis();
int dirtyPhase2 = 0;
for (;;) {
int dirtyPhase1 = mDirty;
dirtyPhase2 |= dirtyPhase1;
mDirty = 0;
updateWakeLockSummaryLocked(dirtyPhase1);
updateUserActivitySummaryLocked(now, dirtyPhase1);
updateAttentiveStateLocked(now, dirtyPhase1);
if (!updateWakefulnessLocked(dirtyPhase1)) {
break;
}
}
// Phase 2: Lock profiles that became inactive/not kept awake.
updateProfilesLocked(now);
// Phase 3: Update display power state.
final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
// Phase 4: Update dream state (depends on display ready signal).
updateDreamLocked(dirtyPhase2, displayBecameReady);
// Phase 5: Send notifications, if needed.
finishWakefulnessChangeIfNeededLocked();
// Phase 6: Update suspend blocker.
// Because we might release the last suspend blocker here, we need to make sure
// we finished everything else first!
updateSuspendBlockerLocked();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
然后调用updateIsPoweredLocked(),此方法中会判断是否充电,充电类型,电池电量等级,会去唤醒屏幕,设置电池状态。
/**
* Updates the value of mIsPowered.
* Sets DIRTY_IS_POWERED if a change occurred.
*/
private void updateIsPoweredLocked(int dirty) {
if ((dirty & DIRTY_BATTERY_STATE) != 0) {
final boolean wasPowered = mIsPowered; //是否充电
final int oldPlugType = mPlugType; // 充电类型
final boolean oldLevelLow = mBatteryLevelLow; //电池电量
mIsPowered = mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
mPlugType = mBatteryManagerInternal.getPlugType();
mBatteryLevel = mBatteryManagerInternal.getBatteryLevel();
mBatteryLevelLow = mBatteryManagerInternal.getBatteryLevelLow();
if (DEBUG_SPEW) {
Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
+ ", mIsPowered=" + mIsPowered
+ ", oldPlugType=" + oldPlugType
+ ", mPlugType=" + mPlugType
+ ", mBatteryLevel=" + mBatteryLevel);
}
if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
mDirty |= DIRTY_IS_POWERED;
// Update wireless dock detection state.
// 更新无线充电头检测状态
final boolean dockedOnWirelessCharger = mWirelessChargerDetector.update(
mIsPowered, mPlugType);
// Treat plugging and unplugging the devices as a user activity.
// Users find it disconcerting when they plug or unplug the device
// and it shuts off right away.
// Some devices also wake the device when plugged or unplugged because
// they don't have a charging LED.
final long now = mClock.uptimeMillis();
// 插拔充电线是否唤醒屏幕PluggedOrUnplugged
if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
dockedOnWirelessCharger)) {
wakeUpNoUpdateLocked(now, PowerManager.WAKE_REASON_PLUGGED_IN,
"android.server.power:PLUGGED:" + mIsPowered, Process.SYSTEM_UID,
mContext.getOpPackageName(), Process.SYSTEM_UID);
}
userActivityNoUpdateLocked(
now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
// only play charging sounds if boot is completed so charging sounds don't play
// with potential notification sounds
// 播放充电提示音
if (mBootCompleted) {
if (mIsPowered && !BatteryManager.isPlugWired(oldPlugType)
&& BatteryManager.isPlugWired(mPlugType)) {
mNotifier.onWiredChargingStarted(mUserId);
} else if (dockedOnWirelessCharger) {
mNotifier.onWirelessChargingStarted(mBatteryLevel, mUserId);
}
}
}
// 设置电池状态
mBatterySaverStateMachine.setBatteryStatus(mIsPowered, mBatteryLevel, mBatteryLevelLow);
}
}
充电提示音在Notifier.java中控制
- 源码路径:frameworks/base/services/core/java/com/android/server/power/Notifier.java
// 无线充电 private void showWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) { // play sounds + haptics playChargingStartedFeedback(userId, true /* wireless */); // show animation if (mShowWirelessChargingAnimationConfig && mStatusBarManagerInternal != null) { mStatusBarManagerInternal.showChargingAnimation(batteryLevel); } mSuspendBlocker.release(); } //有线充电 private void showWiredChargingStarted(@UserIdInt int userId) { playChargingStartedFeedback(userId, false /* wireless */); mSuspendBlocker.release(); } private void playChargingStartedFeedback(@UserIdInt int userId, boolean wireless) { if (!isChargingFeedbackEnabled(userId)) { return; } // vibrate final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0; if (vibrate) { mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES); } // play sound final String soundPath = Settings.Global.getString(mContext.getContentResolver(), wireless ? Settings.Global.WIRELESS_CHARGING_STARTED_SOUND : Settings.Global.CHARGING_STARTED_SOUND); final Uri soundUri = Uri.parse("file://" + soundPath); if (soundUri != null) { final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); if (sfx != null) { sfx.setStreamType(AudioManager.STREAM_SYSTEM); sfx.play(); } } }
上面的代码存在mediaPlayer未释放资源的问题,会导致其它模块播放media时出现(1,-19)
Ringtone 持有一个对应的mediaplayer,虽然Ringtone 每次播放前都释放资源,但是因为RingtoneManager每次返回的是一个新的Ringtone 对象,所以上面代码中,会不断的创建新的nediaplayer,导致出现无法播放问题。
private Ringtone mRingtone; // add ... public Notifier(Looper looper, Context context, IBatteryStats batteryStats, SuspendBlocker suspendBlocker, WindowManagerPolicy policy) { ... initChargingStartedSound(); // add } // add start private void initChargingStartedSound(){ // play sound final String soundPath = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.CHARGING_STARTED_SOUND); if (soundPath != null) { final Uri soundUri = Uri.parse("file://" + soundPath); if (soundUri != null) { mRingtone = RingtoneManager.getRingtone(mContext, soundUri); if (mRingtone != null) { mRingtone.setStreamType(AudioManager.STREAM_SYSTEM); } } } } // add end private void playChargingStartedFeedback(@UserIdInt int userId, boolean wireless) { if (!isChargingFeedbackEnabled(userId)) { return; } // add start // vibrate /* final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0; if (vibrate) { mVibrator.vibrate(CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES); } */ // play sound /*final String soundPath = Settings.Global.getString(mContext.getContentResolver(), wireless ? Settings.Global.WIRELESS_CHARGING_STARTED_SOUND : Settings.Global.CHARGING_STARTED_SOUND); final Uri soundUri = Uri.parse("file://" + soundPath); if (soundUri != null) { final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri); if (sfx != null) { sfx.setStreamType(AudioManager.STREAM_SYSTEM); sfx.play(); } }*/ if (mRingtone != null) { mRingtone.stop(); mRingtone.play(); } // add end }