summaryrefslogtreecommitdiffstats
path: root/services/java/com/android/server/power
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com/android/server/power')
-rw-r--r--services/java/com/android/server/power/DisplayPowerController.java4
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java71
-rw-r--r--services/java/com/android/server/power/WirelessChargerDetector.java320
3 files changed, 355 insertions, 40 deletions
diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java
index 724e126..b5010f2 100644
--- a/services/java/com/android/server/power/DisplayPowerController.java
+++ b/services/java/com/android/server/power/DisplayPowerController.java
@@ -344,7 +344,7 @@ final class DisplayPowerController {
* Creates the display power controller.
*/
public DisplayPowerController(Looper looper, Context context, Notifier notifier,
- LightsService lights, TwilightService twilight,
+ LightsService lights, TwilightService twilight, SensorManager sensorManager,
DisplayManagerService displayManager,
DisplayBlanker displayBlanker,
Callbacks callbacks, Handler callbackHandler) {
@@ -356,7 +356,7 @@ final class DisplayPowerController {
mLights = lights;
mTwilight = twilight;
- mSensorManager = new SystemSensorManager(mHandler.getLooper());
+ mSensorManager = sensorManager;
mDisplayManager = displayManager;
final Resources resources = context.getResources();
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index ec82290..2652739 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -35,6 +35,8 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.SensorManager;
+import android.hardware.SystemSensorManager;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Binder;
@@ -153,11 +155,6 @@ public final class PowerManagerService extends IPowerManager.Stub
// Otherwise the user won't get much screen on time before dimming occurs.
private static final float MAXIMUM_SCREEN_DIM_RATIO = 0.2f;
- // Upper bound on the battery charge percentage in order to consider turning
- // the screen on when the device starts charging wirelessly.
- // See point of use for more details.
- private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95;
-
// The name of the boot animation service in init.rc.
private static final String BOOT_ANIMATION_SERVICE = "bootanim";
@@ -179,6 +176,7 @@ public final class PowerManagerService extends IPowerManager.Stub
private WindowManagerPolicy mPolicy;
private Notifier mNotifier;
private DisplayPowerController mDisplayPowerController;
+ private WirelessChargerDetector mWirelessChargerDetector;
private SettingsObserver mSettingsObserver;
private DreamManagerService mDreamManager;
private LightsService.Light mAttentionLight;
@@ -434,6 +432,8 @@ public final class PowerManagerService extends IPowerManager.Stub
mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
+ SensorManager sensorManager = new SystemSensorManager(mHandler.getLooper());
+
// The notifier runs on the system server's main looper so as not to interfere
// with the animations and other critical functions of the power manager.
mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats,
@@ -441,11 +441,14 @@ public final class PowerManagerService extends IPowerManager.Stub
mScreenOnBlocker, mPolicy);
// The display power controller runs on the power manager service's
- // own handler thread.
+ // own handler thread to ensure timely operation.
mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(),
- mContext, mNotifier, mLightsService, twilight, mDisplayManagerService,
- mDisplayBlanker, mDisplayPowerControllerCallbacks, mHandler);
+ mContext, mNotifier, mLightsService, twilight, sensorManager,
+ mDisplayManagerService, mDisplayBlanker,
+ mDisplayPowerControllerCallbacks, mHandler);
+ mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
+ createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"));
mSettingsObserver = new SettingsObserver(mHandler);
mAttentionLight = mLightsService.getLight(LightsService.LIGHT_ID_ATTENTION);
@@ -1164,65 +1167,51 @@ public final class PowerManagerService extends IPowerManager.Stub
if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
mDirty |= DIRTY_IS_POWERED;
+ // Update wireless dock detection state.
+ final boolean dockedOnWirelessCharger = mWirelessChargerDetector.update(
+ mIsPowered, mPlugType, mBatteryLevel);
+
// 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 = SystemClock.uptimeMillis();
- if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType)) {
+ if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
+ dockedOnWirelessCharger)) {
wakeUpNoUpdateLocked(now);
}
userActivityNoUpdateLocked(
now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
// Tell the notifier whether wireless charging has started so that
- // it can provide feedback to the user. Refer to
- // shouldWakeUpWhenPluggedOrUnpluggedLocked for justification of the
- // heuristics used here.
- if (!wasPowered && mIsPowered
- && mPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS
- && mBatteryLevel < WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT) {
+ // it can provide feedback to the user.
+ if (dockedOnWirelessCharger) {
mNotifier.onWirelessChargingStarted();
}
}
}
}
- private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked(boolean wasPowered, int oldPlugType) {
+ private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked(
+ boolean wasPowered, int oldPlugType, boolean dockedOnWirelessCharger) {
// Don't wake when powered unless configured to do so.
if (!mWakeUpWhenPluggedOrUnpluggedConfig) {
return false;
}
- // FIXME: Need more accurate detection of wireless chargers.
- //
- // We are unable to accurately detect whether the device is resting on the
- // charger unless it is actually receiving power. This causes us some grief
- // because the device might not appear to be plugged into the wireless charger
- // unless it actually charging.
- //
- // To avoid spuriously waking the screen, we apply a special policy to
- // wireless chargers.
- //
- // 1. Don't wake the device when unplugged from wireless charger because
- // it might be that the device is still resting on the wireless charger
- // but is not receiving power anymore because the battery is full.
- //
- // 2. Don't wake the device when plugged into a wireless charger if the
- // battery already appears to be mostly full. This situation may indicate
- // that the device was resting on the charger the whole time and simply
- // wasn't receiving power because the battery was full. We can't tell
- // whether the device was just placed on the charger or whether it has
- // been there for half of the night slowly discharging until it hit
- // the point where it needed to start charging again.
+ // Don't wake when undocked from wireless charger.
+ // See WirelessChargerDetector for justification.
if (wasPowered && !mIsPowered
&& oldPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
return false;
}
+
+ // Don't wake when docked on wireless charger unless we are certain of it.
+ // See WirelessChargerDetector for justification.
if (!wasPowered && mIsPowered
&& mPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS
- && mBatteryLevel >= WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT) {
+ && !dockedOnWirelessCharger) {
return false;
}
@@ -2209,6 +2198,7 @@ public final class PowerManagerService extends IPowerManager.Stub
pw.println("POWER MANAGER (dumpsys power)\n");
final DisplayPowerController dpc;
+ final WirelessChargerDetector wcd;
synchronized (mLock) {
pw.println("Power Manager State:");
pw.println(" mDirty=0x" + Integer.toHexString(mDirty));
@@ -2291,11 +2281,16 @@ public final class PowerManagerService extends IPowerManager.Stub
pw.println("Display Blanker: " + mDisplayBlanker);
dpc = mDisplayPowerController;
+ wcd = mWirelessChargerDetector;
}
if (dpc != null) {
dpc.dump(pw);
}
+
+ if (wcd != null) {
+ wcd.dump(pw);
+ }
}
private SuspendBlocker createSuspendBlockerLocked(String name) {
diff --git a/services/java/com/android/server/power/WirelessChargerDetector.java b/services/java/com/android/server/power/WirelessChargerDetector.java
new file mode 100644
index 0000000..ac6dc3e
--- /dev/null
+++ b/services/java/com/android/server/power/WirelessChargerDetector.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2013 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.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.BatteryManager;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * Implements heuristics to detect docking or undocking from a wireless charger.
+ * <p>
+ * Some devices have wireless charging circuits that are unable to detect when the
+ * device is resting on a wireless charger except when the device is actually
+ * receiving power from the charger. The device may stop receiving power
+ * if the battery is already nearly full or if it is too hot. As a result, we cannot
+ * always rely on the battery service wireless plug signal to accurately indicate
+ * whether the device has been docked or undocked from a wireless charger.
+ * </p><p>
+ * This is a problem because the power manager typically wakes up the screen and
+ * plays a tone when the device is docked in a wireless charger. It is important
+ * for the system to suppress spurious docking and undocking signals because they
+ * can be intrusive for the user (especially if they cause a tone to be played
+ * late at night for no apparent reason).
+ * </p><p>
+ * To avoid spurious signals, we apply some special policies to wireless chargers.
+ * </p><p>
+ * 1. Don't wake the device when undocked from the wireless charger because
+ * it might be that the device is still resting on the wireless charger
+ * but is not receiving power anymore because the battery is full.
+ * Ideally we would wake the device if we could be certain that the user had
+ * picked it up from the wireless charger but due to hardware limitations we
+ * must be more conservative.
+ * </p><p>
+ * 2. Don't wake the device when docked on a wireless charger if the
+ * battery already appears to be mostly full. This situation may indicate
+ * that the device was resting on the charger the whole time and simply
+ * wasn't receiving power because the battery was already full. We can't tell
+ * whether the device was just placed on the charger or whether it has
+ * been there for half of the night slowly discharging until it reached
+ * the point where it needed to start charging again. So we suppress docking
+ * signals that occur when the battery level is above a given threshold.
+ * </p><p>
+ * 3. Don't wake the device when docked on a wireless charger if it does
+ * not appear to have moved since it was last undocked because it may
+ * be that the prior undocking signal was spurious. We use the gravity
+ * sensor to detect this case.
+ * </p>
+ */
+final class WirelessChargerDetector {
+ private static final String TAG = "WirelessChargerDetector";
+ private static final boolean DEBUG = false;
+
+ // Number of nanoseconds per millisecond.
+ private static final long NANOS_PER_MS = 1000000;
+
+ // The minimum amount of time to spend watching the sensor before making
+ // a determination of whether movement occurred.
+ private static final long SETTLE_TIME_NANOS = 500 * NANOS_PER_MS;
+
+ // The minimum number of samples that must be collected.
+ private static final int MIN_SAMPLES = 3;
+
+ // Upper bound on the battery charge percentage in order to consider turning
+ // the screen on when the device starts charging wirelessly.
+ private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95;
+
+ // To detect movement, we compute the angle between the gravity vector
+ // at rest and the current gravity vector. This field specifies the
+ // cosine of the maximum angle variance that we tolerate while at rest.
+ private static final double MOVEMENT_ANGLE_COS_THRESHOLD = Math.cos(5 * Math.PI / 180);
+
+ // Sanity thresholds for the gravity vector.
+ private static final double MIN_GRAVITY = SensorManager.GRAVITY_EARTH - 1.0f;
+ private static final double MAX_GRAVITY = SensorManager.GRAVITY_EARTH + 1.0f;
+
+ private final Object mLock = new Object();
+
+ private final SensorManager mSensorManager;
+ private final SuspendBlocker mSuspendBlocker;
+
+ // The gravity sensor, or null if none.
+ private Sensor mGravitySensor;
+
+ // Previously observed wireless power state.
+ private boolean mPoweredWirelessly;
+
+ // True if the device is thought to be at rest on a wireless charger.
+ private boolean mAtRest;
+
+ // The gravity vector most recently observed while at rest.
+ private float mRestX, mRestY, mRestZ;
+
+ /* These properties are only meaningful while detection is in progress. */
+
+ // True if detection is in progress.
+ // The suspend blocker is held while this is the case.
+ private boolean mDetectionInProgress;
+
+ // True if the rest position should be updated if at rest.
+ // Otherwise, the current rest position is simply checked and cleared if movement
+ // is detected but no new rest position is stored.
+ private boolean mMustUpdateRestPosition;
+
+ // The total number of samples collected.
+ private int mTotalSamples;
+
+ // The number of samples collected that showed evidence of not being at rest.
+ private int mMovingSamples;
+
+ // The time and value of the first sample that was collected.
+ private long mFirstSampleTime;
+ private float mFirstSampleX, mFirstSampleY, mFirstSampleZ;
+
+ public WirelessChargerDetector(SensorManager sensorManager,
+ SuspendBlocker suspendBlocker) {
+ mSensorManager = sensorManager;
+ mSuspendBlocker = suspendBlocker;
+
+ mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
+ }
+
+ public void dump(PrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("Wireless Charger Detector State:");
+ pw.println(" mGravitySensor=" + mGravitySensor);
+ pw.println(" mPoweredWirelessly=" + mPoweredWirelessly);
+ pw.println(" mAtRest=" + mAtRest);
+ pw.println(" mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ);
+ pw.println(" mDetectionInProgress=" + mDetectionInProgress);
+ pw.println(" mMustUpdateRestPosition=" + mMustUpdateRestPosition);
+ pw.println(" mTotalSamples=" + mTotalSamples);
+ pw.println(" mMovingSamples=" + mMovingSamples);
+ pw.println(" mFirstSampleTime=" + mFirstSampleTime);
+ pw.println(" mFirstSampleX=" + mFirstSampleX
+ + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ);
+ }
+ }
+
+ /**
+ * Updates the charging state and returns true if docking was detected.
+ *
+ * @param isPowered True if the device is powered.
+ * @param plugType The current plug type.
+ * @param batteryLevel The current battery level.
+ * @return True if the device is determined to have just been docked on a wireless
+ * charger, after suppressing spurious docking or undocking signals.
+ */
+ public boolean update(boolean isPowered, int plugType, int batteryLevel) {
+ synchronized (mLock) {
+ final boolean wasPoweredWirelessly = mPoweredWirelessly;
+
+ if (isPowered && plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+ // The device is receiving power from the wireless charger.
+ // Update the rest position asynchronously.
+ mPoweredWirelessly = true;
+ mMustUpdateRestPosition = true;
+ startDetectionLocked();
+ } else {
+ // The device may or may not be on the wireless charger depending on whether
+ // the unplug signal that we received was spurious.
+ mPoweredWirelessly = false;
+ if (mAtRest) {
+ if (plugType != 0 && plugType != BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+ // The device was plugged into a new non-wireless power source.
+ // It's safe to assume that it is no longer on the wireless charger.
+ mMustUpdateRestPosition = false;
+ clearAtRestLocked();
+ } else {
+ // The device may still be on the wireless charger but we don't know.
+ // Check whether the device has remained at rest on the charger
+ // so that we will know to ignore the next wireless plug event
+ // if needed.
+ startDetectionLocked();
+ }
+ }
+ }
+
+ // Report that the device has been docked only if the device just started
+ // receiving power wirelessly, has a high enough battery level that we
+ // can be assured that charging was not delayed due to the battery previously
+ // having been full, and the device is not known to already be at rest
+ // on the wireless charger from earlier.
+ return mPoweredWirelessly && !wasPoweredWirelessly
+ && batteryLevel < WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT
+ && !mAtRest;
+ }
+ }
+
+ private void startDetectionLocked() {
+ if (!mDetectionInProgress && mGravitySensor != null) {
+ if (mSensorManager.registerListener(mListener, mGravitySensor,
+ SensorManager.SENSOR_DELAY_UI)) {
+ mSuspendBlocker.acquire();
+ mDetectionInProgress = true;
+ mTotalSamples = 0;
+ mMovingSamples = 0;
+ }
+ }
+ }
+
+ private void processSample(long timeNanos, float x, float y, float z) {
+ synchronized (mLock) {
+ if (!mDetectionInProgress) {
+ return;
+ }
+
+ mTotalSamples += 1;
+ if (mTotalSamples == 1) {
+ // Save information about the first sample collected.
+ mFirstSampleTime = timeNanos;
+ mFirstSampleX = x;
+ mFirstSampleY = y;
+ mFirstSampleZ = z;
+ } else {
+ // Determine whether movement has occurred relative to the first sample.
+ if (hasMoved(mFirstSampleX, mFirstSampleY, mFirstSampleZ, x, y, z)) {
+ mMovingSamples += 1;
+ }
+ }
+
+ // Clear the at rest flag if movement has occurred relative to the rest sample.
+ if (mAtRest && hasMoved(mRestX, mRestY, mRestZ, x, y, z)) {
+ if (DEBUG) {
+ Slog.d(TAG, "No longer at rest: "
+ + "mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
+ + ", x=" + x + ", y=" + y + ", z=" + z);
+ }
+ clearAtRestLocked();
+ }
+
+ // Save the result when done.
+ if (timeNanos - mFirstSampleTime >= SETTLE_TIME_NANOS
+ && mTotalSamples >= MIN_SAMPLES) {
+ mSensorManager.unregisterListener(mListener);
+ if (mMustUpdateRestPosition) {
+ if (mMovingSamples == 0) {
+ mAtRest = true;
+ mRestX = x;
+ mRestY = y;
+ mRestZ = z;
+ } else {
+ clearAtRestLocked();
+ }
+ mMustUpdateRestPosition = false;
+ }
+ mDetectionInProgress = false;
+ mSuspendBlocker.release();
+
+ if (DEBUG) {
+ Slog.d(TAG, "New state: mAtRest=" + mAtRest
+ + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
+ + ", mTotalSamples=" + mTotalSamples
+ + ", mMovingSamples=" + mMovingSamples);
+ }
+ }
+ }
+ }
+
+ private void clearAtRestLocked() {
+ mAtRest = false;
+ mRestX = 0;
+ mRestY = 0;
+ mRestZ = 0;
+ }
+
+ private static boolean hasMoved(float x1, float y1, float z1,
+ float x2, float y2, float z2) {
+ final double dotProduct = (x1 * x2) + (y1 * y2) + (z1 * z2);
+ final double mag1 = Math.sqrt((x1 * x1) + (y1 * y1) + (z1 * z1));
+ final double mag2 = Math.sqrt((x2 * x2) + (y2 * y2) + (z2 * z2));
+ if (mag1 < MIN_GRAVITY || mag1 > MAX_GRAVITY
+ || mag2 < MIN_GRAVITY || mag2 > MAX_GRAVITY) {
+ if (DEBUG) {
+ Slog.d(TAG, "Weird gravity vector: mag1=" + mag1 + ", mag2=" + mag2);
+ }
+ return true;
+ }
+ final boolean moved = (dotProduct < mag1 * mag2 * MOVEMENT_ANGLE_COS_THRESHOLD);
+ if (DEBUG) {
+ Slog.d(TAG, "Check: moved=" + moved
+ + ", x1=" + x1 + ", y1=" + y1 + ", z1=" + z1
+ + ", x2=" + x2 + ", y2=" + y2 + ", z2=" + z2
+ + ", angle=" + (Math.acos(dotProduct / mag1 / mag2) * 180 / Math.PI)
+ + ", dotProduct=" + dotProduct
+ + ", mag1=" + mag1 + ", mag2=" + mag2);
+ }
+ return moved;
+ }
+
+ private final SensorEventListener mListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ processSample(event.timestamp, event.values[0], event.values[1], event.values[2]);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ };
+}