/* * 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.ActivityManagerInternal; import android.app.AppOpsManager; import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.server.EventLogTags; import com.android.server.LocalServices; import android.app.ActivityManagerNative; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.hardware.input.InputManagerInternal; 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.PowerManagerInternal; import android.os.Process; 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; import android.view.inputmethod.InputMethodManagerInternal; /** * Sends broadcasts about important power state changes. *

* 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. *

* The notifier is designed to collapse unnecessary broadcasts when it is not * possible for the system to have observed an intermediate state. *

* 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. *

*/ final 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 final Object mLock = new Object(); private final Context mContext; private final IBatteryStats mBatteryStats; private final IAppOpsService mAppOps; private final SuspendBlocker mSuspendBlocker; private final WindowManagerPolicy mPolicy; private final ActivityManagerInternal mActivityManagerInternal; private final InputManagerInternal mInputManagerInternal; private final InputMethodManagerInternal mInputMethodManagerInternal; 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 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, IAppOpsService appOps, SuspendBlocker suspendBlocker, WindowManagerPolicy policy) { mContext = context; mBatteryStats = batteryStats; mAppOps = appOps; mSuspendBlocker = suspendBlocker; mPolicy = policy; mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class); 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); 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) { } } /** * 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.startOperation(AppOpsManager.getToken(mAppOps), AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName); } } 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.finishOperation(AppOpsManager.getToken(mAppOps), 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) { 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) { } // Handle early behaviors. mInteractive = interactive; mInteractiveChangeReason = reason; 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() { EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0); mPolicy.startedWakingUp(); } }); // 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) { if (mInteractive) { // Finished waking up... mHandler.post(new Runnable() { @Override public void run() { mPolicy.finishedWakingUp(); } }); } 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() { EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0); 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; } } /** * 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(String reason, int reasonUid, String opPackageName, int opUid) { if (DEBUG) { Slog.d(TAG, "onWakeUp: event=" + reason + ", reasonUid=" + reasonUid + " opPackageName=" + opPackageName + " opUid=" + opUid); } try { mBatteryStats.noteWakeUp(reason, reasonUid); if (opPackageName != null) { mAppOps.noteOperation(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName); } } catch (RemoteException ex) { // Ignore } } /** * 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 && 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 (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 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 (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 boolean enabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CHARGING_SOUNDS_ENABLED, 1) != 0; final String soundPath = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.WIRELESS_CHARGING_STARTED_SOUND); if (enabled && 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; case MSG_SCREEN_BRIGHTNESS_BOOST_CHANGED: sendBrightnessBoostChangedBroadcast(); break; } } } }