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