FileDocCategorySizeDatePackage
WirelessChargerDetector.javaAPI DocAndroid 5.1 API15512Thu Mar 12 22:22:42 GMT 2015com.android.server.power

WirelessChargerDetector

public 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 voidclearAtRestLocked()

        mAtRest = false;
        mRestX = 0;
        mRestY = 0;
        mRestZ = 0;
    
public voiddump(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 voidfinishDetectionLocked()

        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 booleanhasMoved(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 voidprocessSampleLocked(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 voidstartDetectionLocked()

        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 booleanupdate(boolean isPowered, int plugType, int batteryLevel)
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.

        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;
        }