WirelessChargerDetectorpublic final class WirelessChargerDetector extends Object Implements heuristics to detect docking or undocking from a wireless charger.
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.
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).
To avoid spurious signals, we apply some special policies to wireless chargers.
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.
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.
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.
|
Fields Summary |
---|
private static final String | TAG | private static final boolean | DEBUG | private static final long | SETTLE_TIME_MILLIS | private static final int | SAMPLING_INTERVAL_MILLIS | private static final int | MIN_SAMPLES | private static final int | WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT | private static final double | MOVEMENT_ANGLE_COS_THRESHOLD | private static final double | MIN_GRAVITY | private static final double | MAX_GRAVITY | private final Object | mLock | private final android.hardware.SensorManager | mSensorManager | private final SuspendBlocker | mSuspendBlocker | private final android.os.Handler | mHandler | private android.hardware.Sensor | mGravitySensor | private boolean | mPoweredWirelessly | private boolean | mAtRest | private float | mRestX | private float | mRestY | private float | mRestZ | private boolean | mDetectionInProgress | private long | mDetectionStartTime | private boolean | mMustUpdateRestPosition | private int | mTotalSamples | private int | mMovingSamples | private float | mFirstSampleX | private float | mFirstSampleY | private float | mFirstSampleZ | private float | mLastSampleX | private float | mLastSampleY | private float | mLastSampleZ | private final android.hardware.SensorEventListener | mListener | private final Runnable | mSensorTimeout |
Constructors Summary |
---|
public WirelessChargerDetector(android.hardware.SensorManager sensorManager, SuspendBlocker suspendBlocker, android.os.Handler handler)
mSensorManager = sensorManager;
mSuspendBlocker = suspendBlocker;
mHandler = handler;
mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
|
Methods Summary |
---|
private void | clearAtRestLocked()
mAtRest = false;
mRestX = 0;
mRestY = 0;
mRestZ = 0;
| public void | dump(java.io.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(" mDetectionStartTime=" + (mDetectionStartTime == 0 ? "0 (never)"
: TimeUtils.formatUptime(mDetectionStartTime)));
pw.println(" mMustUpdateRestPosition=" + mMustUpdateRestPosition);
pw.println(" mTotalSamples=" + mTotalSamples);
pw.println(" mMovingSamples=" + mMovingSamples);
pw.println(" mFirstSampleX=" + mFirstSampleX
+ ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ);
pw.println(" mLastSampleX=" + mLastSampleX
+ ", mLastSampleY=" + mLastSampleY + ", mLastSampleZ=" + mLastSampleZ);
}
| private void | finishDetectionLocked()
if (mDetectionInProgress) {
mSensorManager.unregisterListener(mListener);
mHandler.removeCallbacks(mSensorTimeout);
if (mMustUpdateRestPosition) {
clearAtRestLocked();
if (mTotalSamples < MIN_SAMPLES) {
Slog.w(TAG, "Wireless charger detector is broken. Only received "
+ mTotalSamples + " samples from the gravity sensor but we "
+ "need at least " + MIN_SAMPLES + " and we expect to see "
+ "about " + SETTLE_TIME_MILLIS / SAMPLING_INTERVAL_MILLIS
+ " on average.");
} else if (mMovingSamples == 0) {
mAtRest = true;
mRestX = mLastSampleX;
mRestY = mLastSampleY;
mRestZ = mLastSampleZ;
}
mMustUpdateRestPosition = false;
}
if (DEBUG) {
Slog.d(TAG, "New state: mAtRest=" + mAtRest
+ ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
+ ", mTotalSamples=" + mTotalSamples
+ ", mMovingSamples=" + mMovingSamples);
}
mDetectionInProgress = false;
mSuspendBlocker.release();
}
| 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 void | processSampleLocked(float x, float y, float z)
if (mDetectionInProgress) {
mLastSampleX = x;
mLastSampleY = y;
mLastSampleZ = z;
mTotalSamples += 1;
if (mTotalSamples == 1) {
// Save information about the first sample collected.
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();
}
}
| private void | startDetectionLocked()
if (!mDetectionInProgress && mGravitySensor != null) {
if (mSensorManager.registerListener(mListener, mGravitySensor,
SAMPLING_INTERVAL_MILLIS * 1000)) {
mSuspendBlocker.acquire();
mDetectionInProgress = true;
mDetectionStartTime = SystemClock.uptimeMillis();
mTotalSamples = 0;
mMovingSamples = 0;
Message msg = Message.obtain(mHandler, mSensorTimeout);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, SETTLE_TIME_MILLIS);
}
}
| public boolean | update(boolean isPowered, int plugType, int batteryLevel)Updates the charging state and returns true if docking was detected.
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;
}
|
|