summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/power/Notifier.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/power/Notifier.java')
-rw-r--r--services/core/java/com/android/server/power/Notifier.java546
1 files changed, 546 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
new file mode 100644
index 0000000..264e2e9
--- /dev/null
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -0,0 +1,546 @@
+/*
+ * 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.app.AppOpsManager;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.EventLogTags;
+
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+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.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Slog;
+import android.view.WindowManagerPolicy;
+
+/**
+ * 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>
+ */
+final class Notifier {
+ private static final String TAG = "PowerManagerNotifier";
+
+ private static final boolean DEBUG = false;
+
+ private static final int POWER_STATE_UNKNOWN = 0;
+ private static final int POWER_STATE_AWAKE = 1;
+ private static final int POWER_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 final Object mLock = new Object();
+
+ private final Context mContext;
+ private final IBatteryStats mBatteryStats;
+ private final IAppOpsService mAppOps;
+ private final SuspendBlocker mSuspendBlocker;
+ private final ScreenOnBlocker mScreenOnBlocker;
+ private final WindowManagerPolicy mPolicy;
+
+ private final NotifierHandler mHandler;
+ private final Intent mScreenOnIntent;
+ private final Intent mScreenOffIntent;
+
+ // The current power state.
+ private int mActualPowerState;
+ private int mLastGoToSleepReason;
+
+ // True if there is a pending transition that needs to be reported.
+ private boolean mPendingWakeUpBroadcast;
+ private boolean mPendingGoToSleepBroadcast;
+
+ // The currently broadcasted power state. This reflects what other parts of the
+ // system have observed.
+ private int mBroadcastedPowerState;
+ private boolean mBroadcastInProgress;
+ private long mBroadcastStartTime;
+
+ // True if a user activity message should be sent.
+ private boolean mUserActivityPending;
+
+ // True if the screen on blocker has been acquired.
+ private boolean mScreenOnBlockerAcquired;
+
+ public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
+ IAppOpsService appOps, SuspendBlocker suspendBlocker, ScreenOnBlocker screenOnBlocker,
+ WindowManagerPolicy policy) {
+ mContext = context;
+ mBatteryStats = batteryStats;
+ mAppOps = appOps;
+ mSuspendBlocker = suspendBlocker;
+ mScreenOnBlocker = screenOnBlocker;
+ mPolicy = policy;
+
+ mHandler = new NotifierHandler(looper);
+ mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
+ mScreenOnIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
+ mScreenOffIntent.addFlags(
+ Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+ }
+
+ /**
+ * Called when a wake lock is acquired.
+ */
+ public void onWakeLockAcquired(int flags, String tag, String packageName,
+ int ownerUid, int ownerPid, WorkSource workSource) {
+ if (DEBUG) {
+ Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
+ + "\", packageName=" + packageName
+ + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ + ", workSource=" + workSource);
+ }
+
+ try {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (workSource != null) {
+ mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType);
+ } else {
+ mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType);
+ // XXX need to deal with disabled operations.
+ mAppOps.startOperation(AppOpsManager.getToken(mAppOps),
+ AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
+ }
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Called when a wake lock is released.
+ */
+ public void onWakeLockReleased(int flags, String tag, String packageName,
+ int ownerUid, int ownerPid, WorkSource workSource) {
+ if (DEBUG) {
+ Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
+ + "\", packageName=" + packageName
+ + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+ + ", workSource=" + workSource);
+ }
+
+ try {
+ final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+ if (workSource != null) {
+ mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType);
+ } else {
+ mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType);
+ mAppOps.finishOperation(AppOpsManager.getToken(mAppOps),
+ AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
+ }
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+
+ private static int getBatteryStatsWakeLockMonitorType(int flags) {
+ switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+ case PowerManager.PARTIAL_WAKE_LOCK:
+ case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+ return BatteryStats.WAKE_TYPE_PARTIAL;
+ default:
+ return BatteryStats.WAKE_TYPE_FULL;
+ }
+ }
+
+ /**
+ * Called when the screen is turned on.
+ */
+ public void onScreenOn() {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenOn");
+ }
+
+ try {
+ mBatteryStats.noteScreenOn();
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Called when the screen is turned off.
+ */
+ public void onScreenOff() {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenOff");
+ }
+
+ try {
+ mBatteryStats.noteScreenOff();
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Called when the screen changes brightness.
+ */
+ public void onScreenBrightness(int brightness) {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenBrightness: brightness=" + brightness);
+ }
+
+ try {
+ mBatteryStats.noteScreenBrightness(brightness);
+ } catch (RemoteException ex) {
+ // Ignore
+ }
+ }
+
+ /**
+ * Called when the device is waking up from sleep and the
+ * display is about to be turned on.
+ */
+ public void onWakeUpStarted() {
+ if (DEBUG) {
+ Slog.d(TAG, "onWakeUpStarted");
+ }
+
+ synchronized (mLock) {
+ if (mActualPowerState != POWER_STATE_AWAKE) {
+ mActualPowerState = POWER_STATE_AWAKE;
+ mPendingWakeUpBroadcast = true;
+ if (!mScreenOnBlockerAcquired) {
+ mScreenOnBlockerAcquired = true;
+ mScreenOnBlocker.acquire();
+ }
+ updatePendingBroadcastLocked();
+ }
+ }
+ }
+
+ /**
+ * Called when the device has finished waking up from sleep
+ * and the display has been turned on.
+ */
+ public void onWakeUpFinished() {
+ if (DEBUG) {
+ Slog.d(TAG, "onWakeUpFinished");
+ }
+ }
+
+ /**
+ * Called when the device is going to sleep.
+ */
+ public void onGoToSleepStarted(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "onGoToSleepStarted");
+ }
+
+ synchronized (mLock) {
+ mLastGoToSleepReason = reason;
+ }
+ }
+
+ /**
+ * Called when the device has finished going to sleep and the
+ * display has been turned off.
+ *
+ * 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.
+ */
+ public void onGoToSleepFinished() {
+ if (DEBUG) {
+ Slog.d(TAG, "onGoToSleepFinished");
+ }
+
+ synchronized (mLock) {
+ if (mActualPowerState != POWER_STATE_ASLEEP) {
+ mActualPowerState = POWER_STATE_ASLEEP;
+ mPendingGoToSleepBroadcast = true;
+ if (mUserActivityPending) {
+ mUserActivityPending = false;
+ mHandler.removeMessages(MSG_USER_ACTIVITY);
+ }
+ updatePendingBroadcastLocked();
+ }
+ }
+ }
+
+ /**
+ * 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 wireless charging has started so as to provide user feedback.
+ */
+ public void onWirelessChargingStarted() {
+ if (DEBUG) {
+ Slog.d(TAG, "onWirelessChargingStarted");
+ }
+
+ mSuspendBlocker.acquire();
+ Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+
+ private void updatePendingBroadcastLocked() {
+ if (!mBroadcastInProgress
+ && mActualPowerState != POWER_STATE_UNKNOWN
+ && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
+ || mActualPowerState != mBroadcastedPowerState)) {
+ 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;
+ final int goToSleepReason;
+ synchronized (mLock) {
+ if (mBroadcastedPowerState == POWER_STATE_UNKNOWN) {
+ // Broadcasted power state is unknown. Send wake up.
+ mPendingWakeUpBroadcast = false;
+ mBroadcastedPowerState = POWER_STATE_AWAKE;
+ } else if (mBroadcastedPowerState == POWER_STATE_AWAKE) {
+ // Broadcasted power state is awake. Send asleep if needed.
+ if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
+ || mActualPowerState == POWER_STATE_ASLEEP) {
+ mPendingGoToSleepBroadcast = false;
+ mBroadcastedPowerState = POWER_STATE_ASLEEP;
+ } else {
+ finishPendingBroadcastLocked();
+ return;
+ }
+ } else {
+ // Broadcasted power state is asleep. Send awake if needed.
+ if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
+ || mActualPowerState == POWER_STATE_AWAKE) {
+ mPendingWakeUpBroadcast = false;
+ mBroadcastedPowerState = POWER_STATE_AWAKE;
+ } else {
+ finishPendingBroadcastLocked();
+ return;
+ }
+ }
+
+ mBroadcastStartTime = SystemClock.uptimeMillis();
+ powerState = mBroadcastedPowerState;
+ goToSleepReason = mLastGoToSleepReason;
+ }
+
+ EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1);
+
+ if (powerState == POWER_STATE_AWAKE) {
+ sendWakeUpBroadcast();
+ } else {
+ sendGoToSleepBroadcast(goToSleepReason);
+ }
+ }
+
+ private void sendWakeUpBroadcast() {
+ if (DEBUG) {
+ Slog.d(TAG, "Sending wake up broadcast.");
+ }
+
+ EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0);
+
+ mPolicy.screenTurningOn(mScreenOnListener);
+
+ try {
+ ActivityManagerNative.getDefault().wakingUp();
+ } catch (RemoteException e) {
+ // ignore it
+ }
+
+ if (ActivityManagerNative.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 WindowManagerPolicy.ScreenOnListener mScreenOnListener =
+ new WindowManagerPolicy.ScreenOnListener() {
+ @Override
+ public void onScreenOn() {
+ synchronized (mLock) {
+ if (mScreenOnBlockerAcquired && !mPendingWakeUpBroadcast) {
+ mScreenOnBlockerAcquired = false;
+ mScreenOnBlocker.release();
+ }
+ }
+ }
+ };
+
+ 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(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Sending go to sleep broadcast.");
+ }
+
+ int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER;
+ switch (reason) {
+ case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
+ why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;
+ break;
+ case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
+ why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;
+ break;
+ }
+
+ EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);
+
+ mPolicy.screenTurnedOff(why);
+ try {
+ ActivityManagerNative.getDefault().goingToSleep();
+ } catch (RemoteException e) {
+ // ignore it.
+ }
+
+ if (ActivityManagerNative.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();
+ }
+ };
+
+ private void playWirelessChargingStartedSound() {
+ final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.WIRELESS_CHARGING_STARTED_SOUND);
+ if (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();
+ }
+ }
+ }
+
+ mSuspendBlocker.release();
+ }
+
+ 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:
+ playWirelessChargingStartedSound();
+ break;
+ }
+ }
+ }
+}