blob: b81d969a1bb243643b51220a41ba3b543d9dffbe [file] [log] [blame]
/*
* Copyright (C) 2012 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.
*/
package com.android.server.power;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.hardware.input.InputManagerInternal;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.BatteryStats;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManager.WakeReason;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.WorkSource;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Slog;
import android.util.StatsLog;
import android.view.WindowManagerPolicyConstants.OnReason;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.statusbar.StatusBarManagerInternal;
/**
* Sends broadcasts about important power state changes.
* <p>
* This methods of this class may be called by the power manager service while
* its lock is being held. Internally it takes care of sending broadcasts to
* notify other components of the system or applications asynchronously.
* </p><p>
* The notifier is designed to collapse unnecessary broadcasts when it is not
* possible for the system to have observed an intermediate state.
* </p><p>
* For example, if the device wakes up, goes to sleep, wakes up again and goes to
* sleep again before the wake up notification is sent, then the system will
* be told about only one wake up and sleep. However, we always notify the
* fact that at least one transition occurred. It is especially important to
* tell the system when we go to sleep so that it can lock the keyguard if needed.
* </p>
*/
@VisibleForTesting
public class Notifier {
private static final String TAG = "PowerManagerNotifier";
private static final boolean DEBUG = false;
private static final int INTERACTIVE_STATE_UNKNOWN = 0;
private static final int INTERACTIVE_STATE_AWAKE = 1;
private static final int INTERACTIVE_STATE_ASLEEP = 2;
private static final int MSG_USER_ACTIVITY = 1;
private static final int MSG_BROADCAST = 2;
private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
private static final int MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED = 4;
private static final int MSG_PROFILE_TIMED_OUT = 5;
private static final int MSG_WIRED_CHARGING_STARTED = 6;
private static final long[] WIRELESS_VIBRATION_TIME = {
40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
40, 40, 40, 40, 40, 40, 40 // ramp-down sampling rate = 40ms
};
private static final int[] WIRELESS_VIBRATION_AMPLITUDE = {
1, 4, 11, 25, 44, 67, 91, 114, 123, // ramp-up amplitude (from 0 to 50%)
103, 79, 55, 34, 17, 7, 2 // ramp-up amplitude
};
private static final VibrationEffect WIRELESS_CHARGING_VIBRATION_EFFECT =
VibrationEffect.createWaveform(WIRELESS_VIBRATION_TIME, WIRELESS_VIBRATION_AMPLITUDE,
-1);
private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
private final Object mLock = new Object();
private final Context mContext;
private final IBatteryStats mBatteryStats;
private final AppOpsManager mAppOps;
private final SuspendBlocker mSuspendBlocker;
private final WindowManagerPolicy mPolicy;
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
@Nullable private final StatusBarManagerInternal mStatusBarManagerInternal;
private final TrustManager mTrustManager;
private final Vibrator mVibrator;
private final NotifierHandler mHandler;
private final Intent mScreenOnIntent;
private final Intent mScreenOffIntent;
private final Intent mScreenBrightnessBoostIntent;
// True if the device should suspend when the screen is off due to proximity.
private final boolean mSuspendWhenScreenOffDueToProximityConfig;
// The current interactive state. This is set as soon as an interactive state
// transition begins so as to capture the reason that it happened. At some point
// this state will propagate to the pending state then eventually to the
// broadcasted state over the course of reporting the transition asynchronously.
private boolean mInteractive = true;
private int mInteractiveChangeReason;
private long mInteractiveChangeStartTime; // In SystemClock.uptimeMillis()
private boolean mInteractiveChanging;
// The pending interactive state that we will eventually want to broadcast.
// This is designed so that we can collapse redundant sequences of awake/sleep
// transition pairs while still guaranteeing that at least one transition is observed
// whenever this happens.
private int mPendingInteractiveState;
private boolean mPendingWakeUpBroadcast;
private boolean mPendingGoToSleepBroadcast;
// The currently broadcasted interactive state. This reflects what other parts of the
// system have observed.
private int mBroadcastedInteractiveState;
private boolean mBroadcastInProgress;
private long mBroadcastStartTime;
// True if a user activity message should be sent.
private boolean mUserActivityPending;
public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
mContext = context;
mBatteryStats = batteryStats;
mAppOps = mContext.getSystemService(AppOpsManager.class);
mSuspendBlocker = suspendBlocker;
mPolicy = policy;
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
mTrustManager = mContext.getSystemService(TrustManager.class);
mVibrator = mContext.getSystemService(Vibrator.class);
mHandler = new NotifierHandler(looper);
mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
mScreenOnIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
mScreenOffIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
mScreenBrightnessBoostIntent =
new Intent(PowerManager.ACTION_SCREEN_BRIGHTNESS_BOOST_CHANGED);
mScreenBrightnessBoostIntent.addFlags(
Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
mSuspendWhenScreenOffDueToProximityConfig = context.getResources().getBoolean(
com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
// Initialize interactive state for battery stats.
try {
mBatteryStats.noteInteractive(true);
} catch (RemoteException ex) { }
StatsLog.write(StatsLog.INTERACTIVE_STATE_CHANGED,
StatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON);
}
/**
* Called when a wake lock is acquired.
*/
public void onWakeLockAcquired(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (monitorType >= 0) {
try {
final boolean unimportantForLogging = ownerUid == Process.SYSTEM_UID
&& (flags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
if (workSource != null) {
mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag,
historyTag, monitorType, unimportantForLogging);
} else {
mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, historyTag,
monitorType, unimportantForLogging);
// XXX need to deal with disabled operations.
mAppOps.startOpNoThrow(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
} catch (RemoteException ex) {
// Ignore
}
}
}
public void onLongPartialWakeLockStart(String tag, int ownerUid, WorkSource workSource,
String historyTag) {
if (DEBUG) {
Slog.d(TAG, "onLongPartialWakeLockStart: ownerUid=" + ownerUid
+ ", workSource=" + workSource);
}
try {
if (workSource != null) {
mBatteryStats.noteLongPartialWakelockStartFromSource(tag, historyTag, workSource);
StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, workSource,
tag, historyTag, StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON);
} else {
mBatteryStats.noteLongPartialWakelockStart(tag, historyTag, ownerUid);
StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
ownerUid, null, tag, historyTag,
StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__ON);
}
} catch (RemoteException ex) {
// Ignore
}
}
public void onLongPartialWakeLockFinish(String tag, int ownerUid, WorkSource workSource,
String historyTag) {
if (DEBUG) {
Slog.d(TAG, "onLongPartialWakeLockFinish: ownerUid=" + ownerUid
+ ", workSource=" + workSource);
}
try {
if (workSource != null) {
mBatteryStats.noteLongPartialWakelockFinishFromSource(tag, historyTag, workSource);
StatsLog.write(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED, workSource,
tag, historyTag, StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF);
} else {
mBatteryStats.noteLongPartialWakelockFinish(tag, historyTag, ownerUid);
StatsLog.write_non_chained(StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED,
ownerUid, null, tag, historyTag,
StatsLog.LONG_PARTIAL_WAKELOCK_STATE_CHANGED__STATE__OFF);
}
} catch (RemoteException ex) {
// Ignore
}
}
/**
* Called when a wake lock is changing.
*/
public void onWakeLockChanging(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag,
int newFlags, String newTag, String newPackageName, int newOwnerUid,
int newOwnerPid, WorkSource newWorkSource, String newHistoryTag) {
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
final int newMonitorType = getBatteryStatsWakeLockMonitorType(newFlags);
if (workSource != null && newWorkSource != null
&& monitorType >= 0 && newMonitorType >= 0) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockChanging: flags=" + newFlags + ", tag=\"" + newTag
+ "\", packageName=" + newPackageName
+ ", ownerUid=" + newOwnerUid + ", ownerPid=" + newOwnerPid
+ ", workSource=" + newWorkSource);
}
final boolean unimportantForLogging = newOwnerUid == Process.SYSTEM_UID
&& (newFlags & PowerManager.UNIMPORTANT_FOR_LOGGING) != 0;
try {
mBatteryStats.noteChangeWakelockFromSource(workSource, ownerPid, tag, historyTag,
monitorType, newWorkSource, newOwnerPid, newTag, newHistoryTag,
newMonitorType, unimportantForLogging);
} catch (RemoteException ex) {
// Ignore
}
} else {
onWakeLockReleased(flags, tag, packageName, ownerUid, ownerPid, workSource, historyTag);
onWakeLockAcquired(newFlags, newTag, newPackageName, newOwnerUid, newOwnerPid,
newWorkSource, newHistoryTag);
}
}
/**
* Called when a wake lock is released.
*/
public void onWakeLockReleased(int flags, String tag, String packageName,
int ownerUid, int ownerPid, WorkSource workSource, String historyTag) {
if (DEBUG) {
Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
+ "\", packageName=" + packageName
+ ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ ", workSource=" + workSource);
}
final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
if (monitorType >= 0) {
try {
if (workSource != null) {
mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag,
historyTag, monitorType);
} else {
mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag,
historyTag, monitorType);
mAppOps.finishOp(AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
}
} catch (RemoteException ex) {
// Ignore
}
}
}
private int getBatteryStatsWakeLockMonitorType(int flags) {
switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.PARTIAL_WAKE_LOCK:
return BatteryStats.WAKE_TYPE_PARTIAL;
case PowerManager.SCREEN_DIM_WAKE_LOCK:
case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
return BatteryStats.WAKE_TYPE_FULL;
case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
if (mSuspendWhenScreenOffDueToProximityConfig) {
return -1;
}
return BatteryStats.WAKE_TYPE_PARTIAL;
case PowerManager.DRAW_WAKE_LOCK:
return BatteryStats.WAKE_TYPE_DRAW;
case PowerManager.DOZE_WAKE_LOCK:
// Doze wake locks are an internal implementation detail of the
// communication between dream manager service and power manager
// service. They have no additive battery impact.
return -1;
default:
return -1;
}
}
/**
* Notifies that the device is changing wakefulness.
* This function may be called even if the previous change hasn't finished in
* which case it will assume that the state did not fully converge before the
* next transition began and will recover accordingly.
*/
public void onWakefulnessChangeStarted(final int wakefulness, int reason, long eventTime) {
final boolean interactive = PowerManagerInternal.isInteractive(wakefulness);
if (DEBUG) {
Slog.d(TAG, "onWakefulnessChangeStarted: wakefulness=" + wakefulness
+ ", reason=" + reason + ", interactive=" + interactive);
}
// Tell the activity manager about changes in wakefulness, not just interactivity.
// It needs more granularity than other components.
mHandler.post(new Runnable() {
@Override
public void run() {
mActivityManagerInternal.onWakefulnessChanged(wakefulness);
}
});
// Handle any early interactive state changes.
// Finish pending incomplete ones from a previous cycle.
if (mInteractive != interactive) {
// Finish up late behaviors if needed.
if (mInteractiveChanging) {
handleLateInteractiveChange();
}
// Start input as soon as we start waking up or going to sleep.
mInputManagerInternal.setInteractive(interactive);
mInputMethodManagerInternal.setInteractive(interactive);
// Notify battery stats.
try {
mBatteryStats.noteInteractive(interactive);
} catch (RemoteException ex) { }
StatsLog.write(StatsLog.INTERACTIVE_STATE_CHANGED,
interactive ? StatsLog.INTERACTIVE_STATE_CHANGED__STATE__ON :
StatsLog.INTERACTIVE_STATE_CHANGED__STATE__OFF);
// Handle early behaviors.
mInteractive = interactive;
mInteractiveChangeReason = reason;
mInteractiveChangeStartTime = eventTime;
mInteractiveChanging = true;
handleEarlyInteractiveChange();
}
}
/**
* Notifies that the device has finished changing wakefulness.
*/
public void onWakefulnessChangeFinished() {
if (DEBUG) {
Slog.d(TAG, "onWakefulnessChangeFinished");
}
if (mInteractiveChanging) {
mInteractiveChanging = false;
handleLateInteractiveChange();
}
}
/**
* Handle early interactive state changes such as getting applications or the lock
* screen running and ready for the user to see (such as when turning on the screen).
*/
private void handleEarlyInteractiveChange() {
synchronized (mLock) {
if (mInteractive) {
// Waking up...
mHandler.post(new Runnable() {
@Override
public void run() {
final int why = translateOnReason(mInteractiveChangeReason);
mPolicy.startedWakingUp(why);
}
});
// Send interactive broadcast.
mPendingInteractiveState = INTERACTIVE_STATE_AWAKE;
mPendingWakeUpBroadcast = true;
updatePendingBroadcastLocked();
} else {
// Going to sleep...
// Tell the policy that we started going to sleep.
final int why = translateOffReason(mInteractiveChangeReason);
mHandler.post(new Runnable() {
@Override
public void run() {
mPolicy.startedGoingToSleep(why);
}
});
}
}
}
/**
* Handle late interactive state changes once they are finished so that the system can
* finish pending transitions (such as turning the screen off) before causing
* applications to change state visibly.
*/
private void handleLateInteractiveChange() {
synchronized (mLock) {
final int interactiveChangeLatency =
(int) (SystemClock.uptimeMillis() - mInteractiveChangeStartTime);
if (mInteractive) {
// Finished waking up...
final int why = translateOnReason(mInteractiveChangeReason);
mHandler.post(new Runnable() {
@Override
public void run() {
LogMaker log = new LogMaker(MetricsEvent.SCREEN);
log.setType(MetricsEvent.TYPE_OPEN);
log.setSubtype(why);
log.setLatency(interactiveChangeLatency);
log.addTaggedData(
MetricsEvent.FIELD_SCREEN_WAKE_REASON, mInteractiveChangeReason);
MetricsLogger.action(log);
EventLogTags.writePowerScreenState(1, 0, 0, 0, interactiveChangeLatency);
mPolicy.finishedWakingUp(why);
}
});
} else {
// Finished going to sleep...
// This is a good time to make transitions that we don't want the user to see,
// such as bringing the key guard to focus. There's no guarantee for this
// however because the user could turn the device on again at any time.
// Some things may need to be protected by other mechanisms that defer screen on.
// Cancel pending user activity.
if (mUserActivityPending) {
mUserActivityPending = false;
mHandler.removeMessages(MSG_USER_ACTIVITY);
}
// Tell the policy we finished going to sleep.
final int why = translateOffReason(mInteractiveChangeReason);
mHandler.post(new Runnable() {
@Override
public void run() {
LogMaker log = new LogMaker(MetricsEvent.SCREEN);
log.setType(MetricsEvent.TYPE_CLOSE);
log.setSubtype(why);
log.setLatency(interactiveChangeLatency);
log.addTaggedData(
MetricsEvent.FIELD_SCREEN_SLEEP_REASON, mInteractiveChangeReason);
MetricsLogger.action(log);
EventLogTags.writePowerScreenState(0, why, 0, 0, interactiveChangeLatency);
mPolicy.finishedGoingToSleep(why);
}
});
// Send non-interactive broadcast.
mPendingInteractiveState = INTERACTIVE_STATE_ASLEEP;
mPendingGoToSleepBroadcast = true;
updatePendingBroadcastLocked();
}
}
}
private static int translateOffReason(int reason) {
switch (reason) {
case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
return WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;
case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
return WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;
default:
return WindowManagerPolicy.OFF_BECAUSE_OF_USER;
}
}
private static @OnReason int translateOnReason(@WakeReason int reason) {
switch (reason) {
case PowerManager.WAKE_REASON_POWER_BUTTON:
case PowerManager.WAKE_REASON_PLUGGED_IN:
case PowerManager.WAKE_REASON_GESTURE:
case PowerManager.WAKE_REASON_CAMERA_LAUNCH:
case PowerManager.WAKE_REASON_WAKE_KEY:
case PowerManager.WAKE_REASON_WAKE_MOTION:
case PowerManager.WAKE_REASON_LID:
return WindowManagerPolicy.ON_BECAUSE_OF_USER;
case PowerManager.WAKE_REASON_APPLICATION:
return WindowManagerPolicy.ON_BECAUSE_OF_APPLICATION;
default:
return WindowManagerPolicy.ON_BECAUSE_OF_UNKNOWN;
}
}
/**
* Called when screen brightness boost begins or ends.
*/
public void onScreenBrightnessBoostChanged() {
if (DEBUG) {
Slog.d(TAG, "onScreenBrightnessBoostChanged");
}
mSuspendBlocker.acquire();
Message msg = mHandler.obtainMessage(MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
/**
* Called when there has been user activity.
*/
public void onUserActivity(int event, int uid) {
if (DEBUG) {
Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
}
try {
mBatteryStats.noteUserActivity(uid, event);
} catch (RemoteException ex) {
// Ignore
}
synchronized (mLock) {
if (!mUserActivityPending) {
mUserActivityPending = true;
Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
}
/**
* Called when the screen has turned on.
*/
public void onWakeUp(int reason, String details, int reasonUid, String opPackageName,
int opUid) {
if (DEBUG) {
Slog.d(TAG, "onWakeUp: reason=" + PowerManager.wakeReasonToString(reason)
+ ", details=" + details + ", reasonUid=" + reasonUid
+ " opPackageName=" + opPackageName + " opUid=" + opUid);
}
try {
mBatteryStats.noteWakeUp(details, reasonUid);
if (opPackageName != null) {
mAppOps.noteOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName);
}
} catch (RemoteException ex) {
// Ignore
}
}
/**
* Called when profile screen lock timeout has expired.
*/
public void onProfileTimeout(@UserIdInt int userId) {
final Message msg = mHandler.obtainMessage(MSG_PROFILE_TIMED_OUT);
msg.setAsynchronous(true);
msg.arg1 = userId;
mHandler.sendMessage(msg);
}
/**
* Called when wireless charging has started - to provide user feedback (sound and visual).
*/
public void onWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "onWirelessChargingStarted");
}
mSuspendBlocker.acquire();
Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
msg.setAsynchronous(true);
msg.arg1 = batteryLevel;
msg.arg2 = userId;
mHandler.sendMessage(msg);
}
/**
* Called when wired charging has started - to provide user feedback
*/
public void onWiredChargingStarted(@UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "onWiredChargingStarted");
}
mSuspendBlocker.acquire();
Message msg = mHandler.obtainMessage(MSG_WIRED_CHARGING_STARTED);
msg.setAsynchronous(true);
msg.arg1 = userId;
mHandler.sendMessage(msg);
}
private void updatePendingBroadcastLocked() {
if (!mBroadcastInProgress
&& mPendingInteractiveState != INTERACTIVE_STATE_UNKNOWN
&& (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
|| mPendingInteractiveState != mBroadcastedInteractiveState)) {
mBroadcastInProgress = true;
mSuspendBlocker.acquire();
Message msg = mHandler.obtainMessage(MSG_BROADCAST);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
}
private void finishPendingBroadcastLocked() {
mBroadcastInProgress = false;
mSuspendBlocker.release();
}
private void sendUserActivity() {
synchronized (mLock) {
if (!mUserActivityPending) {
return;
}
mUserActivityPending = false;
}
mPolicy.userActivity();
}
private void sendNextBroadcast() {
final int powerState;
synchronized (mLock) {
if (mBroadcastedInteractiveState == INTERACTIVE_STATE_UNKNOWN) {
// Broadcasted power state is unknown. Send wake up.
mPendingWakeUpBroadcast = false;
mBroadcastedInteractiveState = INTERACTIVE_STATE_AWAKE;
} else if (mBroadcastedInteractiveState == INTERACTIVE_STATE_AWAKE) {
// Broadcasted power state is awake. Send asleep if needed.
if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
|| mPendingInteractiveState == INTERACTIVE_STATE_ASLEEP) {
mPendingGoToSleepBroadcast = false;
mBroadcastedInteractiveState = INTERACTIVE_STATE_ASLEEP;
} else {
finishPendingBroadcastLocked();
return;
}
} else {
// Broadcasted power state is asleep. Send awake if needed.
if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
|| mPendingInteractiveState == INTERACTIVE_STATE_AWAKE) {
mPendingWakeUpBroadcast = false;
mBroadcastedInteractiveState = INTERACTIVE_STATE_AWAKE;
} else {
finishPendingBroadcastLocked();
return;
}
}
mBroadcastStartTime = SystemClock.uptimeMillis();
powerState = mBroadcastedInteractiveState;
}
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1);
if (powerState == INTERACTIVE_STATE_AWAKE) {
sendWakeUpBroadcast();
} else {
sendGoToSleepBroadcast();
}
}
private void sendBrightnessBoostChangedBroadcast() {
if (DEBUG) {
Slog.d(TAG, "Sending brightness boost changed broadcast.");
}
mContext.sendOrderedBroadcastAsUser(mScreenBrightnessBoostIntent, UserHandle.ALL, null,
mScreeBrightnessBoostChangedDone, mHandler, 0, null, null);
}
private final BroadcastReceiver mScreeBrightnessBoostChangedDone = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mSuspendBlocker.release();
}
};
private void sendWakeUpBroadcast() {
if (DEBUG) {
Slog.d(TAG, "Sending wake up broadcast.");
}
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
mWakeUpBroadcastDone, mHandler, 0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
sendNextBroadcast();
}
}
private final BroadcastReceiver mWakeUpBroadcastDone = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1,
SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
sendNextBroadcast();
}
};
private void sendGoToSleepBroadcast() {
if (DEBUG) {
Slog.d(TAG, "Sending go to sleep broadcast.");
}
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
mGoToSleepBroadcastDone, mHandler, 0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
sendNextBroadcast();
}
}
private final BroadcastReceiver mGoToSleepBroadcastDone = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0,
SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
sendNextBroadcast();
}
};
/**
* If enabled, plays a sound and/or vibration when wireless or non-wireless charging has started
*/
private void playChargingStartedFeedback(@UserIdInt int userId) {
playChargingStartedVibration(userId);
final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.CHARGING_STARTED_SOUND);
if (isChargingFeedbackEnabled(userId) && soundPath != null) {
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();
}
}
}
}
private void showWirelessChargingStarted(int batteryLevel, @UserIdInt int userId) {
playChargingStartedFeedback(userId);
if (mStatusBarManagerInternal != null) {
mStatusBarManagerInternal.showChargingAnimation(batteryLevel);
}
mSuspendBlocker.release();
}
private void showWiredChargingStarted(@UserIdInt int userId) {
playChargingStartedFeedback(userId);
mSuspendBlocker.release();
}
private void lockProfile(@UserIdInt int userId) {
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
private void playChargingStartedVibration(@UserIdInt int userId) {
final boolean vibrateEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
if (vibrateEnabled && isChargingFeedbackEnabled(userId)) {
mVibrator.vibrate(WIRELESS_CHARGING_VIBRATION_EFFECT, VIBRATION_ATTRIBUTES);
}
}
private boolean isChargingFeedbackEnabled(@UserIdInt int userId) {
final boolean enabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.CHARGING_SOUNDS_ENABLED, 1, userId) != 0;
final boolean dndOff = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
== Settings.Global.ZEN_MODE_OFF;
return enabled && dndOff;
}
private final class NotifierHandler extends Handler {
public NotifierHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_USER_ACTIVITY:
sendUserActivity();
break;
case MSG_BROADCAST:
sendNextBroadcast();
break;
case MSG_WIRELESS_CHARGING_STARTED:
showWirelessChargingStarted(msg.arg1, msg.arg2);
break;
case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED:
sendBrightnessBoostChangedBroadcast();
break;
case MSG_PROFILE_TIMED_OUT:
lockProfile(msg.arg1);
break;
case MSG_WIRED_CHARGING_STARTED:
showWiredChargingStarted(msg.arg1);
break;
}
}
}
}