Home | History | Annotate | Download | only in power
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.power;
     18 
     19 import android.hardware.Sensor;
     20 import android.hardware.SensorEvent;
     21 import android.hardware.SensorEventListener;
     22 import android.hardware.SensorManager;
     23 import android.os.BatteryManager;
     24 import android.util.Slog;
     25 
     26 import java.io.PrintWriter;
     27 
     28 /**
     29  * Implements heuristics to detect docking or undocking from a wireless charger.
     30  * <p>
     31  * Some devices have wireless charging circuits that are unable to detect when the
     32  * device is resting on a wireless charger except when the device is actually
     33  * receiving power from the charger.  The device may stop receiving power
     34  * if the battery is already nearly full or if it is too hot.  As a result, we cannot
     35  * always rely on the battery service wireless plug signal to accurately indicate
     36  * whether the device has been docked or undocked from a wireless charger.
     37  * </p><p>
     38  * This is a problem because the power manager typically wakes up the screen and
     39  * plays a tone when the device is docked in a wireless charger.  It is important
     40  * for the system to suppress spurious docking and undocking signals because they
     41  * can be intrusive for the user (especially if they cause a tone to be played
     42  * late at night for no apparent reason).
     43  * </p><p>
     44  * To avoid spurious signals, we apply some special policies to wireless chargers.
     45  * </p><p>
     46  * 1. Don't wake the device when undocked from the wireless charger because
     47  * it might be that the device is still resting on the wireless charger
     48  * but is not receiving power anymore because the battery is full.
     49  * Ideally we would wake the device if we could be certain that the user had
     50  * picked it up from the wireless charger but due to hardware limitations we
     51  * must be more conservative.
     52  * </p><p>
     53  * 2. Don't wake the device when docked on a wireless charger if the
     54  * battery already appears to be mostly full.  This situation may indicate
     55  * that the device was resting on the charger the whole time and simply
     56  * wasn't receiving power because the battery was already full.  We can't tell
     57  * whether the device was just placed on the charger or whether it has
     58  * been there for half of the night slowly discharging until it reached
     59  * the point where it needed to start charging again.  So we suppress docking
     60  * signals that occur when the battery level is above a given threshold.
     61  * </p><p>
     62  * 3. Don't wake the device when docked on a wireless charger if it does
     63  * not appear to have moved since it was last undocked because it may
     64  * be that the prior undocking signal was spurious.  We use the gravity
     65  * sensor to detect this case.
     66  * </p>
     67  */
     68 final class WirelessChargerDetector {
     69     private static final String TAG = "WirelessChargerDetector";
     70     private static final boolean DEBUG = false;
     71 
     72     // Number of nanoseconds per millisecond.
     73     private static final long NANOS_PER_MS = 1000000;
     74 
     75     // The minimum amount of time to spend watching the sensor before making
     76     // a determination of whether movement occurred.
     77     private static final long SETTLE_TIME_NANOS = 500 * NANOS_PER_MS;
     78 
     79     // The minimum number of samples that must be collected.
     80     private static final int MIN_SAMPLES = 3;
     81 
     82     // Upper bound on the battery charge percentage in order to consider turning
     83     // the screen on when the device starts charging wirelessly.
     84     private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95;
     85 
     86     // To detect movement, we compute the angle between the gravity vector
     87     // at rest and the current gravity vector.  This field specifies the
     88     // cosine of the maximum angle variance that we tolerate while at rest.
     89     private static final double MOVEMENT_ANGLE_COS_THRESHOLD = Math.cos(5 * Math.PI / 180);
     90 
     91     // Sanity thresholds for the gravity vector.
     92     private static final double MIN_GRAVITY = SensorManager.GRAVITY_EARTH - 1.0f;
     93     private static final double MAX_GRAVITY = SensorManager.GRAVITY_EARTH + 1.0f;
     94 
     95     private final Object mLock = new Object();
     96 
     97     private final SensorManager mSensorManager;
     98     private final SuspendBlocker mSuspendBlocker;
     99 
    100     // The gravity sensor, or null if none.
    101     private Sensor mGravitySensor;
    102 
    103     // Previously observed wireless power state.
    104     private boolean mPoweredWirelessly;
    105 
    106     // True if the device is thought to be at rest on a wireless charger.
    107     private boolean mAtRest;
    108 
    109     // The gravity vector most recently observed while at rest.
    110     private float mRestX, mRestY, mRestZ;
    111 
    112     /* These properties are only meaningful while detection is in progress. */
    113 
    114     // True if detection is in progress.
    115     // The suspend blocker is held while this is the case.
    116     private boolean mDetectionInProgress;
    117 
    118     // True if the rest position should be updated if at rest.
    119     // Otherwise, the current rest position is simply checked and cleared if movement
    120     // is detected but no new rest position is stored.
    121     private boolean mMustUpdateRestPosition;
    122 
    123     // The total number of samples collected.
    124     private int mTotalSamples;
    125 
    126     // The number of samples collected that showed evidence of not being at rest.
    127     private int mMovingSamples;
    128 
    129     // The time and value of the first sample that was collected.
    130     private long mFirstSampleTime;
    131     private float mFirstSampleX, mFirstSampleY, mFirstSampleZ;
    132 
    133     public WirelessChargerDetector(SensorManager sensorManager,
    134             SuspendBlocker suspendBlocker) {
    135         mSensorManager = sensorManager;
    136         mSuspendBlocker = suspendBlocker;
    137 
    138         mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
    139     }
    140 
    141     public void dump(PrintWriter pw) {
    142         synchronized (mLock) {
    143             pw.println();
    144             pw.println("Wireless Charger Detector State:");
    145             pw.println("  mGravitySensor=" + mGravitySensor);
    146             pw.println("  mPoweredWirelessly=" + mPoweredWirelessly);
    147             pw.println("  mAtRest=" + mAtRest);
    148             pw.println("  mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ);
    149             pw.println("  mDetectionInProgress=" + mDetectionInProgress);
    150             pw.println("  mMustUpdateRestPosition=" + mMustUpdateRestPosition);
    151             pw.println("  mTotalSamples=" + mTotalSamples);
    152             pw.println("  mMovingSamples=" + mMovingSamples);
    153             pw.println("  mFirstSampleTime=" + mFirstSampleTime);
    154             pw.println("  mFirstSampleX=" + mFirstSampleX
    155                     + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ);
    156         }
    157     }
    158 
    159     /**
    160      * Updates the charging state and returns true if docking was detected.
    161      *
    162      * @param isPowered True if the device is powered.
    163      * @param plugType The current plug type.
    164      * @param batteryLevel The current battery level.
    165      * @return True if the device is determined to have just been docked on a wireless
    166      * charger, after suppressing spurious docking or undocking signals.
    167      */
    168     public boolean update(boolean isPowered, int plugType, int batteryLevel) {
    169         synchronized (mLock) {
    170             final boolean wasPoweredWirelessly = mPoweredWirelessly;
    171 
    172             if (isPowered && plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
    173                 // The device is receiving power from the wireless charger.
    174                 // Update the rest position asynchronously.
    175                 mPoweredWirelessly = true;
    176                 mMustUpdateRestPosition = true;
    177                 startDetectionLocked();
    178             } else {
    179                 // The device may or may not be on the wireless charger depending on whether
    180                 // the unplug signal that we received was spurious.
    181                 mPoweredWirelessly = false;
    182                 if (mAtRest) {
    183                     if (plugType != 0 && plugType != BatteryManager.BATTERY_PLUGGED_WIRELESS) {
    184                         // The device was plugged into a new non-wireless power source.
    185                         // It's safe to assume that it is no longer on the wireless charger.
    186                         mMustUpdateRestPosition = false;
    187                         clearAtRestLocked();
    188                     } else {
    189                         // The device may still be on the wireless charger but we don't know.
    190                         // Check whether the device has remained at rest on the charger
    191                         // so that we will know to ignore the next wireless plug event
    192                         // if needed.
    193                         startDetectionLocked();
    194                     }
    195                 }
    196             }
    197 
    198             // Report that the device has been docked only if the device just started
    199             // receiving power wirelessly, has a high enough battery level that we
    200             // can be assured that charging was not delayed due to the battery previously
    201             // having been full, and the device is not known to already be at rest
    202             // on the wireless charger from earlier.
    203             return mPoweredWirelessly && !wasPoweredWirelessly
    204                     && batteryLevel < WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT
    205                     && !mAtRest;
    206         }
    207     }
    208 
    209     private void startDetectionLocked() {
    210         if (!mDetectionInProgress && mGravitySensor != null) {
    211             if (mSensorManager.registerListener(mListener, mGravitySensor,
    212                     SensorManager.SENSOR_DELAY_UI)) {
    213                 mSuspendBlocker.acquire();
    214                 mDetectionInProgress = true;
    215                 mTotalSamples = 0;
    216                 mMovingSamples = 0;
    217             }
    218         }
    219     }
    220 
    221     private void processSample(long timeNanos, float x, float y, float z) {
    222         synchronized (mLock) {
    223             if (!mDetectionInProgress) {
    224                 return;
    225             }
    226 
    227             mTotalSamples += 1;
    228             if (mTotalSamples == 1) {
    229                 // Save information about the first sample collected.
    230                 mFirstSampleTime = timeNanos;
    231                 mFirstSampleX = x;
    232                 mFirstSampleY = y;
    233                 mFirstSampleZ = z;
    234             } else {
    235                 // Determine whether movement has occurred relative to the first sample.
    236                 if (hasMoved(mFirstSampleX, mFirstSampleY, mFirstSampleZ, x, y, z)) {
    237                     mMovingSamples += 1;
    238                 }
    239             }
    240 
    241             // Clear the at rest flag if movement has occurred relative to the rest sample.
    242             if (mAtRest && hasMoved(mRestX, mRestY, mRestZ, x, y, z)) {
    243                 if (DEBUG) {
    244                     Slog.d(TAG, "No longer at rest: "
    245                             + "mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
    246                             + ", x=" + x + ", y=" + y + ", z=" + z);
    247                 }
    248                 clearAtRestLocked();
    249             }
    250 
    251             // Save the result when done.
    252             if (timeNanos - mFirstSampleTime >= SETTLE_TIME_NANOS
    253                     && mTotalSamples >= MIN_SAMPLES) {
    254                 mSensorManager.unregisterListener(mListener);
    255                 if (mMustUpdateRestPosition) {
    256                     if (mMovingSamples == 0) {
    257                         mAtRest = true;
    258                         mRestX = x;
    259                         mRestY = y;
    260                         mRestZ = z;
    261                     } else {
    262                         clearAtRestLocked();
    263                     }
    264                     mMustUpdateRestPosition = false;
    265                 }
    266                 mDetectionInProgress = false;
    267                 mSuspendBlocker.release();
    268 
    269                 if (DEBUG) {
    270                     Slog.d(TAG, "New state: mAtRest=" + mAtRest
    271                             + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
    272                             + ", mTotalSamples=" + mTotalSamples
    273                             + ", mMovingSamples=" + mMovingSamples);
    274                 }
    275             }
    276         }
    277     }
    278 
    279     private void clearAtRestLocked() {
    280         mAtRest = false;
    281         mRestX = 0;
    282         mRestY = 0;
    283         mRestZ = 0;
    284     }
    285 
    286     private static boolean hasMoved(float x1, float y1, float z1,
    287             float x2, float y2, float z2) {
    288         final double dotProduct = (x1 * x2) + (y1 * y2) + (z1 * z2);
    289         final double mag1 = Math.sqrt((x1 * x1) + (y1 * y1) + (z1 * z1));
    290         final double mag2 = Math.sqrt((x2 * x2) + (y2 * y2) + (z2 * z2));
    291         if (mag1 < MIN_GRAVITY || mag1 > MAX_GRAVITY
    292                 || mag2 < MIN_GRAVITY || mag2 > MAX_GRAVITY) {
    293             if (DEBUG) {
    294                 Slog.d(TAG, "Weird gravity vector: mag1=" + mag1 + ", mag2=" + mag2);
    295             }
    296             return true;
    297         }
    298         final boolean moved = (dotProduct < mag1 * mag2 * MOVEMENT_ANGLE_COS_THRESHOLD);
    299         if (DEBUG) {
    300             Slog.d(TAG, "Check: moved=" + moved
    301                     + ", x1=" + x1 + ", y1=" + y1 + ", z1=" + z1
    302                     + ", x2=" + x2 + ", y2=" + y2 + ", z2=" + z2
    303                     + ", angle=" + (Math.acos(dotProduct / mag1 / mag2) * 180 / Math.PI)
    304                     + ", dotProduct=" + dotProduct
    305                     + ", mag1=" + mag1 + ", mag2=" + mag2);
    306         }
    307         return moved;
    308     }
    309 
    310     private final SensorEventListener mListener = new SensorEventListener() {
    311         @Override
    312         public void onSensorChanged(SensorEvent event) {
    313             processSample(event.timestamp, event.values[0], event.values[1], event.values[2]);
    314         }
    315 
    316         @Override
    317         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    318         }
    319     };
    320 }
    321