PageRenderTime 55ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/services/java/com/android/server/power/WirelessChargerDetector.java

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