diff options
Diffstat (limited to 'services/core/java/com/android/server/power/Notifier.java')
-rw-r--r-- | services/core/java/com/android/server/power/Notifier.java | 546 |
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; + } + } + } +} |