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.os.Handler;
     25 import android.os.Message;
     26 import android.os.SystemClock;
     27 import android.service.power.WirelessChargerDetectorProto;
     28 import android.util.Slog;
     29 import android.util.TimeUtils;
     30 import android.util.proto.ProtoOutputStream;
     31 
     32 import java.io.PrintWriter;
     33 
     34 /**
     35  * Implements heuristics to detect docking or undocking from a wireless charger.
     36  * <p>
     37  * Some devices have wireless charging circuits that are unable to detect when the
     38  * device is resting on a wireless charger except when the device is actually
     39  * receiving power from the charger.  The device may stop receiving power
     40  * if the battery is already nearly full or if it is too hot.  As a result, we cannot
     41  * always rely on the battery service wireless plug signal to accurately indicate
     42  * whether the device has been docked or undocked from a wireless charger.
     43  * </p><p>
     44  * This is a problem because the power manager typically wakes up the screen and
     45  * plays a tone when the device is docked in a wireless charger.  It is important
     46  * for the system to suppress spurious docking and undocking signals because they
     47  * can be intrusive for the user (especially if they cause a tone to be played
     48  * late at night for no apparent reason).
     49  * </p><p>
     50  * To avoid spurious signals, we apply some special policies to wireless chargers.
     51  * </p><p>
     52  * 1. Don't wake the device when undocked from the wireless charger because
     53  * it might be that the device is still resting on the wireless charger
     54  * but is not receiving power anymore because the battery is full.
     55  * Ideally we would wake the device if we could be certain that the user had
     56  * picked it up from the wireless charger but due to hardware limitations we
     57  * must be more conservative.
     58  * </p><p>
     59  * 2. Don't wake the device when docked on a wireless charger if the
     60  * battery already appears to be mostly full.  This situation may indicate
     61  * that the device was resting on the charger the whole time and simply
     62  * wasn't receiving power because the battery was already full.  We can't tell
     63  * whether the device was just placed on the charger or whether it has
     64  * been there for half of the night slowly discharging until it reached
     65  * the point where it needed to start charging again.  So we suppress docking
     66  * signals that occur when the battery level is above a given threshold.
     67  * </p><p>
     68  * 3. Don't wake the device when docked on a wireless charger if it does
     69  * not appear to have moved since it was last undocked because it may
     70  * be that the prior undocking signal was spurious.  We use the gravity
     71  * sensor to detect this case.
     72  * </p>
     73  */
     74 final class WirelessChargerDetector {
     75     private static final String TAG = "WirelessChargerDetector";
     76     private static final boolean DEBUG = false;
     77 
     78     // The minimum amount of time to spend watching the sensor before making
     79     // a determination of whether movement occurred.
     80     private static final long SETTLE_TIME_MILLIS = 800;
     81 
     82     // The sensor sampling interval.
     83     private static final int SAMPLING_INTERVAL_MILLIS = 50;
     84 
     85     // The minimum number of samples that must be collected.
     86     private static final int MIN_SAMPLES = 3;
     87 
     88     // Upper bound on the battery charge percentage in order to consider turning
     89     // the screen on when the device starts charging wirelessly.
     90     private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95;
     91 
     92     // To detect movement, we compute the angle between the gravity vector
     93     // at rest and the current gravity vector.  This field specifies the
     94     // cosine of the maximum angle variance that we tolerate while at rest.
     95     private static final double MOVEMENT_ANGLE_COS_THRESHOLD = Math.cos(5 * Math.PI / 180);
     96 
     97     // Sanity thresholds for the gravity vector.
     98     private static final double MIN_GRAVITY = SensorManager.GRAVITY_EARTH - 1.0f;
     99     private static final double MAX_GRAVITY = SensorManager.GRAVITY_EARTH + 1.0f;
    100 
    101     private final Object mLock = new Object();
    102 
    103     private final SensorManager mSensorManager;
    104     private final SuspendBlocker mSuspendBlocker;
    105     private final Handler mHandler;
    106 
    107     // The gravity sensor, or null if none.
    108     private Sensor mGravitySensor;
    109 
    110     // Previously observed wireless power state.
    111     private boolean mPoweredWirelessly;
    112 
    113     // True if the device is thought to be at rest on a wireless charger.
    114     private boolean mAtRest;
    115 
    116     // The gravity vector most recently observed while at rest.
    117     private float mRestX, mRestY, mRestZ;
    118 
    119     /* These properties are only meaningful while detection is in progress. */
    120 
    121     // True if detection is in progress.
    122     // The suspend blocker is held while this is the case.
    123     private boolean mDetectionInProgress;
    124 
    125     // The time when detection was last performed.
    126     private long mDetectionStartTime;
    127 
    128     // True if the rest position should be updated if at rest.
    129     // Otherwise, the current rest position is simply checked and cleared if movement
    130     // is detected but no new rest position is stored.
    131     private boolean mMustUpdateRestPosition;
    132 
    133     // The total number of samples collected.
    134     private int mTotalSamples;
    135 
    136     // The number of samples collected that showed evidence of not being at rest.
    137     private int mMovingSamples;
    138 
    139     // The value of the first sample that was collected.
    140     private float mFirstSampleX, mFirstSampleY, mFirstSampleZ;
    141 
    142     // The value of the last sample that was collected.
    143     private float mLastSampleX, mLastSampleY, mLastSampleZ;
    144 
    145     public WirelessChargerDetector(SensorManager sensorManager,
    146             SuspendBlocker suspendBlocker, Handler handler) {
    147         mSensorManager = sensorManager;
    148         mSuspendBlocker = suspendBlocker;
    149         mHandler = handler;
    150 
    151         mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
    152     }
    153 
    154     public void dump(PrintWriter pw) {
    155         synchronized (mLock) {
    156             pw.println();
    157             pw.println("Wireless Charger Detector State:");
    158             pw.println("  mGravitySensor=" + mGravitySensor);
    159             pw.println("  mPoweredWirelessly=" + mPoweredWirelessly);
    160             pw.println("  mAtRest=" + mAtRest);
    161             pw.println("  mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ);
    162             pw.println("  mDetectionInProgress=" + mDetectionInProgress);
    163             pw.println("  mDetectionStartTime=" + (mDetectionStartTime == 0 ? "0 (never)"
    164                     : TimeUtils.formatUptime(mDetectionStartTime)));
    165             pw.println("  mMustUpdateRestPosition=" + mMustUpdateRestPosition);
    166             pw.println("  mTotalSamples=" + mTotalSamples);
    167             pw.println("  mMovingSamples=" + mMovingSamples);
    168             pw.println("  mFirstSampleX=" + mFirstSampleX
    169                     + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ);
    170             pw.println("  mLastSampleX=" + mLastSampleX
    171                     + ", mLastSampleY=" + mLastSampleY + ", mLastSampleZ=" + mLastSampleZ);
    172         }
    173     }
    174 
    175     public void writeToProto(ProtoOutputStream proto, long fieldId) {
    176         final long wcdToken = proto.start(fieldId);
    177         synchronized (mLock) {
    178             proto.write(WirelessChargerDetectorProto.IS_POWERED_WIRELESSLY, mPoweredWirelessly);
    179             proto.write(WirelessChargerDetectorProto.IS_AT_REST, mAtRest);
    180 
    181             final long restVectorToken = proto.start(WirelessChargerDetectorProto.REST);
    182             proto.write(WirelessChargerDetectorProto.VectorProto.X, mRestX);
    183             proto.write(WirelessChargerDetectorProto.VectorProto.Y, mRestY);
    184             proto.write(WirelessChargerDetectorProto.VectorProto.Z, mRestZ);
    185             proto.end(restVectorToken);
    186 
    187             proto.write(
    188                     WirelessChargerDetectorProto.IS_DETECTION_IN_PROGRESS, mDetectionInProgress);
    189             proto.write(WirelessChargerDetectorProto.DETECTION_START_TIME_MS, mDetectionStartTime);
    190             proto.write(
    191                     WirelessChargerDetectorProto.IS_MUST_UPDATE_REST_POSITION,
    192                     mMustUpdateRestPosition);
    193             proto.write(WirelessChargerDetectorProto.TOTAL_SAMPLES, mTotalSamples);
    194             proto.write(WirelessChargerDetectorProto.MOVING_SAMPLES, mMovingSamples);
    195 
    196             final long firstSampleVectorToken =
    197                     proto.start(WirelessChargerDetectorProto.FIRST_SAMPLE);
    198             proto.write(WirelessChargerDetectorProto.VectorProto.X, mFirstSampleX);
    199             proto.write(WirelessChargerDetectorProto.VectorProto.Y, mFirstSampleY);
    200             proto.write(WirelessChargerDetectorProto.VectorProto.Z, mFirstSampleZ);
    201             proto.end(firstSampleVectorToken);
    202 
    203             final long lastSampleVectorToken =
    204                     proto.start(WirelessChargerDetectorProto.LAST_SAMPLE);
    205             proto.write(WirelessChargerDetectorProto.VectorProto.X, mLastSampleX);
    206             proto.write(WirelessChargerDetectorProto.VectorProto.Y, mLastSampleY);
    207             proto.write(WirelessChargerDetectorProto.VectorProto.Z, mLastSampleZ);
    208             proto.end(lastSampleVectorToken);
    209         }
    210         proto.end(wcdToken);
    211     }
    212 
    213     /**
    214      * Updates the charging state and returns true if docking was detected.
    215      *
    216      * @param isPowered True if the device is powered.
    217      * @param plugType The current plug type.
    218      * @param batteryLevel The current battery level.
    219      * @return True if the device is determined to have just been docked on a wireless
    220      * charger, after suppressing spurious docking or undocking signals.
    221      */
    222     public boolean update(boolean isPowered, int plugType, int batteryLevel) {
    223         synchronized (mLock) {
    224             final boolean wasPoweredWirelessly = mPoweredWirelessly;
    225 
    226             if (isPowered && plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
    227                 // The device is receiving power from the wireless charger.
    228                 // Update the rest position asynchronously.
    229                 mPoweredWirelessly = true;
    230                 mMustUpdateRestPosition = true;
    231                 startDetectionLocked();
    232             } else {
    233                 // The device may or may not be on the wireless charger depending on whether
    234                 // the unplug signal that we received was spurious.
    235                 mPoweredWirelessly = false;
    236                 if (mAtRest) {
    237                     if (plugType != 0 && plugType != BatteryManager.BATTERY_PLUGGED_WIRELESS) {
    238                         // The device was plugged into a new non-wireless power source.
    239                         // It's safe to assume that it is no longer on the wireless charger.
    240                         mMustUpdateRestPosition = false;
    241                         clearAtRestLocked();
    242                     } else {
    243                         // The device may still be on the wireless charger but we don't know.
    244                         // Check whether the device has remained at rest on the charger
    245                         // so that we will know to ignore the next wireless plug event
    246                         // if needed.
    247                         startDetectionLocked();
    248                     }
    249                 }
    250             }
    251 
    252             // Report that the device has been docked only if the device just started
    253             // receiving power wirelessly, has a high enough battery level that we
    254             // can be assured that charging was not delayed due to the battery previously
    255             // having been full, and the device is not known to already be at rest
    256             // on the wireless charger from earlier.
    257             return mPoweredWirelessly && !wasPoweredWirelessly
    258                     && batteryLevel < WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT
    259                     && !mAtRest;
    260         }
    261     }
    262 
    263     private void startDetectionLocked() {
    264         if (!mDetectionInProgress && mGravitySensor != null) {
    265             if (mSensorManager.registerListener(mListener, mGravitySensor,
    266                     SAMPLING_INTERVAL_MILLIS * 1000)) {
    267                 mSuspendBlocker.acquire();
    268                 mDetectionInProgress = true;
    269                 mDetectionStartTime = SystemClock.uptimeMillis();
    270                 mTotalSamples = 0;
    271                 mMovingSamples = 0;
    272 
    273                 Message msg = Message.obtain(mHandler, mSensorTimeout);
    274                 msg.setAsynchronous(true);
    275                 mHandler.sendMessageDelayed(msg, SETTLE_TIME_MILLIS);
    276             }
    277         }
    278     }
    279 
    280     private void finishDetectionLocked() {
    281         if (mDetectionInProgress) {
    282             mSensorManager.unregisterListener(mListener);
    283             mHandler.removeCallbacks(mSensorTimeout);
    284 
    285             if (mMustUpdateRestPosition) {
    286                 clearAtRestLocked();
    287                 if (mTotalSamples < MIN_SAMPLES) {
    288                     Slog.w(TAG, "Wireless charger detector is broken.  Only received "
    289                             + mTotalSamples + " samples from the gravity sensor but we "
    290                             + "need at least " + MIN_SAMPLES + " and we expect to see "
    291                             + "about " + SETTLE_TIME_MILLIS / SAMPLING_INTERVAL_MILLIS
    292                             + " on average.");
    293                 } else if (mMovingSamples == 0) {
    294                     mAtRest = true;
    295                     mRestX = mLastSampleX;
    296                     mRestY = mLastSampleY;
    297                     mRestZ = mLastSampleZ;
    298                 }
    299                 mMustUpdateRestPosition = false;
    300             }
    301 
    302             if (DEBUG) {
    303                 Slog.d(TAG, "New state: mAtRest=" + mAtRest
    304                         + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
    305                         + ", mTotalSamples=" + mTotalSamples
    306                         + ", mMovingSamples=" + mMovingSamples);
    307             }
    308 
    309             mDetectionInProgress = false;
    310             mSuspendBlocker.release();
    311         }
    312     }
    313 
    314     private void processSampleLocked(float x, float y, float z) {
    315         if (mDetectionInProgress) {
    316             mLastSampleX = x;
    317             mLastSampleY = y;
    318             mLastSampleZ = z;
    319 
    320             mTotalSamples += 1;
    321             if (mTotalSamples == 1) {
    322                 // Save information about the first sample collected.
    323                 mFirstSampleX = x;
    324                 mFirstSampleY = y;
    325                 mFirstSampleZ = z;
    326             } else {
    327                 // Determine whether movement has occurred relative to the first sample.
    328                 if (hasMoved(mFirstSampleX, mFirstSampleY, mFirstSampleZ, x, y, z)) {
    329                     mMovingSamples += 1;
    330                 }
    331             }
    332 
    333             // Clear the at rest flag if movement has occurred relative to the rest sample.
    334             if (mAtRest && hasMoved(mRestX, mRestY, mRestZ, x, y, z)) {
    335                 if (DEBUG) {
    336                     Slog.d(TAG, "No longer at rest: "
    337                             + "mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
    338                             + ", x=" + x + ", y=" + y + ", z=" + z);
    339                 }
    340                 clearAtRestLocked();
    341             }
    342         }
    343     }
    344 
    345     private void clearAtRestLocked() {
    346         mAtRest = false;
    347         mRestX = 0;
    348         mRestY = 0;
    349         mRestZ = 0;
    350     }
    351 
    352     private static boolean hasMoved(float x1, float y1, float z1,
    353             float x2, float y2, float z2) {
    354         final double dotProduct = (x1 * x2) + (y1 * y2) + (z1 * z2);
    355         final double mag1 = Math.sqrt((x1 * x1) + (y1 * y1) + (z1 * z1));
    356         final double mag2 = Math.sqrt((x2 * x2) + (y2 * y2) + (z2 * z2));
    357         if (mag1 < MIN_GRAVITY || mag1 > MAX_GRAVITY
    358                 || mag2 < MIN_GRAVITY || mag2 > MAX_GRAVITY) {
    359             if (DEBUG) {
    360                 Slog.d(TAG, "Weird gravity vector: mag1=" + mag1 + ", mag2=" + mag2);
    361             }
    362             return true;
    363         }
    364         final boolean moved = (dotProduct < mag1 * mag2 * MOVEMENT_ANGLE_COS_THRESHOLD);
    365         if (DEBUG) {
    366             Slog.d(TAG, "Check: moved=" + moved
    367                     + ", x1=" + x1 + ", y1=" + y1 + ", z1=" + z1
    368                     + ", x2=" + x2 + ", y2=" + y2 + ", z2=" + z2
    369                     + ", angle=" + (Math.acos(dotProduct / mag1 / mag2) * 180 / Math.PI)
    370                     + ", dotProduct=" + dotProduct
    371                     + ", mag1=" + mag1 + ", mag2=" + mag2);
    372         }
    373         return moved;
    374     }
    375 
    376     private final SensorEventListener mListener = new SensorEventListener() {
    377         @Override
    378         public void onSensorChanged(SensorEvent event) {
    379             synchronized (mLock) {
    380                 processSampleLocked(event.values[0], event.values[1], event.values[2]);
    381             }
    382         }
    383 
    384         @Override
    385         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    386         }
    387     };
    388 
    389     private final Runnable mSensorTimeout = new Runnable() {
    390         @Override
    391         public void run() {
    392             synchronized (mLock) {
    393                 finishDetectionLocked();
    394             }
    395         }
    396     };
    397 }
    398