1 /* 2 * Copyright (C) 2014 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.systemui.doze; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.app.UiModeManager; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.content.res.Configuration; 27 import android.hardware.Sensor; 28 import android.hardware.SensorEvent; 29 import android.hardware.SensorEventListener; 30 import android.hardware.SensorManager; 31 import android.hardware.TriggerEvent; 32 import android.hardware.TriggerEventListener; 33 import android.media.AudioAttributes; 34 import android.os.Handler; 35 import android.os.PowerManager; 36 import android.os.SystemClock; 37 import android.os.Vibrator; 38 import android.service.dreams.DreamService; 39 import android.util.Log; 40 import android.view.Display; 41 42 import com.android.systemui.SystemUIApplication; 43 import com.android.systemui.statusbar.phone.DozeParameters; 44 import com.android.systemui.statusbar.phone.DozeParameters.PulseSchedule; 45 46 import java.io.FileDescriptor; 47 import java.io.PrintWriter; 48 import java.util.Date; 49 50 public class DozeService extends DreamService { 51 private static final String TAG = "DozeService"; 52 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 53 54 private static final String ACTION_BASE = "com.android.systemui.doze"; 55 private static final String PULSE_ACTION = ACTION_BASE + ".pulse"; 56 private static final String NOTIFICATION_PULSE_ACTION = ACTION_BASE + ".notification_pulse"; 57 private static final String EXTRA_INSTANCE = "instance"; 58 59 /** 60 * Earliest time we pulse due to a notification light after the service started. 61 * 62 * <p>Incoming notification light events during the blackout period are 63 * delayed to the earliest time defined by this constant.</p> 64 * 65 * <p>This delay avoids a pulse immediately after screen off, at which 66 * point the notification light is re-enabled again by NoMan.</p> 67 */ 68 private static final int EARLIEST_LIGHT_PULSE_AFTER_START_MS = 10 * 1000; 69 70 private final String mTag = String.format(TAG + ".%08x", hashCode()); 71 private final Context mContext = this; 72 private final DozeParameters mDozeParameters = new DozeParameters(mContext); 73 private final Handler mHandler = new Handler(); 74 75 private DozeHost mHost; 76 private SensorManager mSensors; 77 private TriggerSensor mSigMotionSensor; 78 private TriggerSensor mPickupSensor; 79 private PowerManager mPowerManager; 80 private PowerManager.WakeLock mWakeLock; 81 private AlarmManager mAlarmManager; 82 private UiModeManager mUiModeManager; 83 private boolean mDreaming; 84 private boolean mPulsing; 85 private boolean mBroadcastReceiverRegistered; 86 private boolean mDisplayStateSupported; 87 private boolean mNotificationLightOn; 88 private boolean mPowerSaveActive; 89 private boolean mCarMode; 90 private long mNotificationPulseTime; 91 private long mLastScheduleResetTime; 92 private long mEarliestPulseDueToLight; 93 private int mScheduleResetsRemaining; 94 DozeService()95 public DozeService() { 96 if (DEBUG) Log.d(mTag, "new DozeService()"); 97 setDebug(DEBUG); 98 } 99 100 @Override dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args)101 protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) { 102 super.dumpOnHandler(fd, pw, args); 103 pw.print(" mDreaming: "); pw.println(mDreaming); 104 pw.print(" mPulsing: "); pw.println(mPulsing); 105 pw.print(" mWakeLock: held="); pw.println(mWakeLock.isHeld()); 106 pw.print(" mHost: "); pw.println(mHost); 107 pw.print(" mBroadcastReceiverRegistered: "); pw.println(mBroadcastReceiverRegistered); 108 pw.print(" mSigMotionSensor: "); pw.println(mSigMotionSensor); 109 pw.print(" mPickupSensor:"); pw.println(mPickupSensor); 110 pw.print(" mDisplayStateSupported: "); pw.println(mDisplayStateSupported); 111 pw.print(" mNotificationLightOn: "); pw.println(mNotificationLightOn); 112 pw.print(" mPowerSaveActive: "); pw.println(mPowerSaveActive); 113 pw.print(" mCarMode: "); pw.println(mCarMode); 114 pw.print(" mNotificationPulseTime: "); pw.println(mNotificationPulseTime); 115 pw.print(" mScheduleResetsRemaining: "); pw.println(mScheduleResetsRemaining); 116 mDozeParameters.dump(pw); 117 } 118 119 @Override onCreate()120 public void onCreate() { 121 if (DEBUG) Log.d(mTag, "onCreate"); 122 super.onCreate(); 123 124 if (getApplication() instanceof SystemUIApplication) { 125 final SystemUIApplication app = (SystemUIApplication) getApplication(); 126 mHost = app.getComponent(DozeHost.class); 127 } 128 if (mHost == null) Log.w(TAG, "No doze service host found."); 129 130 setWindowless(true); 131 132 mSensors = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); 133 mSigMotionSensor = new TriggerSensor(Sensor.TYPE_SIGNIFICANT_MOTION, 134 mDozeParameters.getPulseOnSigMotion(), mDozeParameters.getVibrateOnSigMotion(), 135 DozeLog.PULSE_REASON_SENSOR_SIGMOTION); 136 mPickupSensor = new TriggerSensor(Sensor.TYPE_PICK_UP_GESTURE, 137 mDozeParameters.getPulseOnPickup(), mDozeParameters.getVibrateOnPickup(), 138 DozeLog.PULSE_REASON_SENSOR_PICKUP); 139 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 140 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); 141 mWakeLock.setReferenceCounted(true); 142 mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 143 mDisplayStateSupported = mDozeParameters.getDisplayStateSupported(); 144 mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE); 145 turnDisplayOff(); 146 } 147 148 @Override onAttachedToWindow()149 public void onAttachedToWindow() { 150 if (DEBUG) Log.d(mTag, "onAttachedToWindow"); 151 super.onAttachedToWindow(); 152 } 153 154 @Override onDreamingStarted()155 public void onDreamingStarted() { 156 super.onDreamingStarted(); 157 158 if (mHost == null) { 159 finish(); 160 return; 161 } 162 163 mPowerSaveActive = mHost.isPowerSaveActive(); 164 mCarMode = mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR; 165 if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze() + " mPowerSaveActive=" 166 + mPowerSaveActive + " mCarMode=" + mCarMode); 167 if (mPowerSaveActive) { 168 finishToSavePower(); 169 return; 170 } 171 if (mCarMode) { 172 finishForCarMode(); 173 return; 174 } 175 176 mDreaming = true; 177 rescheduleNotificationPulse(false /*predicate*/); // cancel any pending pulse alarms 178 mEarliestPulseDueToLight = System.currentTimeMillis() + EARLIEST_LIGHT_PULSE_AFTER_START_MS; 179 listenForPulseSignals(true); 180 181 // Ask the host to get things ready to start dozing. 182 // Once ready, we call startDozing() at which point the CPU may suspend 183 // and we will need to acquire a wakelock to do work. 184 mHost.startDozing(new Runnable() { 185 @Override 186 public void run() { 187 if (mDreaming) { 188 startDozing(); 189 190 // From this point until onDreamingStopped we will need to hold a 191 // wakelock whenever we are doing work. Note that we never call 192 // stopDozing because can we just keep dozing until the bitter end. 193 } 194 } 195 }); 196 } 197 198 @Override onDreamingStopped()199 public void onDreamingStopped() { 200 if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing()); 201 super.onDreamingStopped(); 202 203 if (mHost == null) { 204 return; 205 } 206 207 mDreaming = false; 208 listenForPulseSignals(false); 209 210 // Tell the host that it's over. 211 mHost.stopDozing(); 212 } 213 requestPulse(final int reason)214 private void requestPulse(final int reason) { 215 if (mHost != null && mDreaming && !mPulsing) { 216 // Let the host know we want to pulse. Wait for it to be ready, then 217 // turn the screen on. When finished, turn the screen off again. 218 // Here we need a wakelock to stay awake until the pulse is finished. 219 mWakeLock.acquire(); 220 mPulsing = true; 221 if (!mDozeParameters.getProxCheckBeforePulse()) { 222 // skip proximity check 223 continuePulsing(reason); 224 return; 225 } 226 final long start = SystemClock.uptimeMillis(); 227 final boolean nonBlocking = reason == DozeLog.PULSE_REASON_SENSOR_PICKUP 228 && mDozeParameters.getPickupPerformsProxCheck(); 229 if (nonBlocking) { 230 // proximity check is only done to capture statistics, continue pulsing 231 continuePulsing(reason); 232 } 233 // perform a proximity check 234 new ProximityCheck() { 235 @Override 236 public void onProximityResult(int result) { 237 final boolean isNear = result == RESULT_NEAR; 238 final long end = SystemClock.uptimeMillis(); 239 DozeLog.traceProximityResult(mContext, isNear, end - start, reason); 240 if (nonBlocking) { 241 // we already continued 242 return; 243 } 244 // avoid pulsing in pockets 245 if (isNear) { 246 mPulsing = false; 247 mWakeLock.release(); 248 return; 249 } 250 251 // not in-pocket, continue pulsing 252 continuePulsing(reason); 253 } 254 }.check(); 255 } 256 } 257 continuePulsing(int reason)258 private void continuePulsing(int reason) { 259 if (mHost.isPulsingBlocked()) { 260 mPulsing = false; 261 mWakeLock.release(); 262 return; 263 } 264 mHost.pulseWhileDozing(new DozeHost.PulseCallback() { 265 @Override 266 public void onPulseStarted() { 267 if (mPulsing && mDreaming) { 268 turnDisplayOn(); 269 } 270 } 271 272 @Override 273 public void onPulseFinished() { 274 if (mPulsing && mDreaming) { 275 mPulsing = false; 276 turnDisplayOff(); 277 } 278 mWakeLock.release(); // needs to be unconditional to balance acquire 279 } 280 }, reason); 281 } 282 turnDisplayOff()283 private void turnDisplayOff() { 284 if (DEBUG) Log.d(mTag, "Display off"); 285 setDozeScreenState(Display.STATE_OFF); 286 } 287 turnDisplayOn()288 private void turnDisplayOn() { 289 if (DEBUG) Log.d(mTag, "Display on"); 290 setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON); 291 } 292 finishToSavePower()293 private void finishToSavePower() { 294 Log.w(mTag, "Exiting ambient mode due to low power battery saver"); 295 finish(); 296 } 297 finishForCarMode()298 private void finishForCarMode() { 299 Log.w(mTag, "Exiting ambient mode, not allowed in car mode"); 300 finish(); 301 } 302 listenForPulseSignals(boolean listen)303 private void listenForPulseSignals(boolean listen) { 304 if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen); 305 mSigMotionSensor.setListening(listen); 306 mPickupSensor.setListening(listen); 307 listenForBroadcasts(listen); 308 listenForNotifications(listen); 309 } 310 listenForBroadcasts(boolean listen)311 private void listenForBroadcasts(boolean listen) { 312 if (listen) { 313 final IntentFilter filter = new IntentFilter(PULSE_ACTION); 314 filter.addAction(NOTIFICATION_PULSE_ACTION); 315 filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); 316 mContext.registerReceiver(mBroadcastReceiver, filter); 317 mBroadcastReceiverRegistered = true; 318 } else { 319 if (mBroadcastReceiverRegistered) { 320 mContext.unregisterReceiver(mBroadcastReceiver); 321 } 322 mBroadcastReceiverRegistered = false; 323 } 324 } 325 listenForNotifications(boolean listen)326 private void listenForNotifications(boolean listen) { 327 if (listen) { 328 resetNotificationResets(); 329 mHost.addCallback(mHostCallback); 330 331 // Continue to pulse for existing LEDs. 332 mNotificationLightOn = mHost.isNotificationLightOn(); 333 if (mNotificationLightOn) { 334 updateNotificationPulseDueToLight(); 335 } 336 } else { 337 mHost.removeCallback(mHostCallback); 338 } 339 } 340 resetNotificationResets()341 private void resetNotificationResets() { 342 if (DEBUG) Log.d(mTag, "resetNotificationResets"); 343 mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets(); 344 } 345 updateNotificationPulseDueToLight()346 private void updateNotificationPulseDueToLight() { 347 long timeMs = System.currentTimeMillis(); 348 timeMs = Math.max(timeMs, mEarliestPulseDueToLight); 349 updateNotificationPulse(timeMs); 350 } 351 updateNotificationPulse(long notificationTimeMs)352 private void updateNotificationPulse(long notificationTimeMs) { 353 if (DEBUG) Log.d(mTag, "updateNotificationPulse notificationTimeMs=" + notificationTimeMs); 354 if (!mDozeParameters.getPulseOnNotifications()) return; 355 if (mScheduleResetsRemaining <= 0) { 356 if (DEBUG) Log.d(mTag, "No more schedule resets remaining"); 357 return; 358 } 359 final long pulseDuration = mDozeParameters.getPulseDuration(false /*pickup*/); 360 boolean pulseImmediately = System.currentTimeMillis() >= notificationTimeMs; 361 if ((notificationTimeMs - mLastScheduleResetTime) >= pulseDuration) { 362 mScheduleResetsRemaining--; 363 mLastScheduleResetTime = notificationTimeMs; 364 } else if (!pulseImmediately){ 365 if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule"); 366 return; 367 } 368 if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining); 369 mNotificationPulseTime = notificationTimeMs; 370 if (pulseImmediately) { 371 DozeLog.traceNotificationPulse(0); 372 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); 373 } 374 // schedule the rest of the pulses 375 rescheduleNotificationPulse(true /*predicate*/); 376 } 377 notificationPulseIntent(long instance)378 private PendingIntent notificationPulseIntent(long instance) { 379 return PendingIntent.getBroadcast(mContext, 0, 380 new Intent(NOTIFICATION_PULSE_ACTION) 381 .setPackage(getPackageName()) 382 .putExtra(EXTRA_INSTANCE, instance) 383 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND), 384 PendingIntent.FLAG_UPDATE_CURRENT); 385 } 386 rescheduleNotificationPulse(boolean predicate)387 private void rescheduleNotificationPulse(boolean predicate) { 388 if (DEBUG) Log.d(mTag, "rescheduleNotificationPulse predicate=" + predicate); 389 final PendingIntent notificationPulseIntent = notificationPulseIntent(0); 390 mAlarmManager.cancel(notificationPulseIntent); 391 if (!predicate) { 392 if (DEBUG) Log.d(mTag, " don't reschedule: predicate is false"); 393 return; 394 } 395 final PulseSchedule schedule = mDozeParameters.getPulseSchedule(); 396 if (schedule == null) { 397 if (DEBUG) Log.d(mTag, " don't reschedule: schedule is null"); 398 return; 399 } 400 final long now = System.currentTimeMillis(); 401 final long time = schedule.getNextTime(now, mNotificationPulseTime); 402 if (time <= 0) { 403 if (DEBUG) Log.d(mTag, " don't reschedule: time is " + time); 404 return; 405 } 406 final long delta = time - now; 407 if (delta <= 0) { 408 if (DEBUG) Log.d(mTag, " don't reschedule: delta is " + delta); 409 return; 410 } 411 final long instance = time - mNotificationPulseTime; 412 if (DEBUG) Log.d(mTag, "Scheduling pulse " + instance + " in " + delta + "ms for " 413 + new Date(time)); 414 mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, notificationPulseIntent(instance)); 415 } 416 triggerEventToString(TriggerEvent event)417 private static String triggerEventToString(TriggerEvent event) { 418 if (event == null) return null; 419 final StringBuilder sb = new StringBuilder("TriggerEvent[") 420 .append(event.timestamp).append(',') 421 .append(event.sensor.getName()); 422 if (event.values != null) { 423 for (int i = 0; i < event.values.length; i++) { 424 sb.append(',').append(event.values[i]); 425 } 426 } 427 return sb.append(']').toString(); 428 } 429 430 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 431 @Override 432 public void onReceive(Context context, Intent intent) { 433 if (PULSE_ACTION.equals(intent.getAction())) { 434 if (DEBUG) Log.d(mTag, "Received pulse intent"); 435 requestPulse(DozeLog.PULSE_REASON_INTENT); 436 } 437 if (NOTIFICATION_PULSE_ACTION.equals(intent.getAction())) { 438 final long instance = intent.getLongExtra(EXTRA_INSTANCE, -1); 439 if (DEBUG) Log.d(mTag, "Received notification pulse intent instance=" + instance); 440 DozeLog.traceNotificationPulse(instance); 441 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION); 442 rescheduleNotificationPulse(mNotificationLightOn); 443 } 444 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { 445 mCarMode = true; 446 if (mCarMode && mDreaming) { 447 finishForCarMode(); 448 } 449 } 450 } 451 }; 452 453 private final DozeHost.Callback mHostCallback = new DozeHost.Callback() { 454 @Override 455 public void onNewNotifications() { 456 if (DEBUG) Log.d(mTag, "onNewNotifications (noop)"); 457 // noop for now 458 } 459 460 @Override 461 public void onBuzzBeepBlinked() { 462 if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked"); 463 updateNotificationPulse(System.currentTimeMillis()); 464 } 465 466 @Override 467 public void onNotificationLight(boolean on) { 468 if (DEBUG) Log.d(mTag, "onNotificationLight on=" + on); 469 if (mNotificationLightOn == on) return; 470 mNotificationLightOn = on; 471 if (mNotificationLightOn) { 472 updateNotificationPulseDueToLight(); 473 } 474 } 475 476 @Override 477 public void onPowerSaveChanged(boolean active) { 478 mPowerSaveActive = active; 479 if (mPowerSaveActive && mDreaming) { 480 finishToSavePower(); 481 } 482 } 483 }; 484 485 private class TriggerSensor extends TriggerEventListener { 486 private final Sensor mSensor; 487 private final boolean mConfigured; 488 private final boolean mDebugVibrate; 489 private final int mPulseReason; 490 491 private boolean mRequested; 492 private boolean mRegistered; 493 private boolean mDisabled; 494 TriggerSensor(int type, boolean configured, boolean debugVibrate, int pulseReason)495 public TriggerSensor(int type, boolean configured, boolean debugVibrate, int pulseReason) { 496 mSensor = mSensors.getDefaultSensor(type); 497 mConfigured = configured; 498 mDebugVibrate = debugVibrate; 499 mPulseReason = pulseReason; 500 } 501 setListening(boolean listen)502 public void setListening(boolean listen) { 503 if (mRequested == listen) return; 504 mRequested = listen; 505 updateListener(); 506 } 507 setDisabled(boolean disabled)508 public void setDisabled(boolean disabled) { 509 if (mDisabled == disabled) return; 510 mDisabled = disabled; 511 updateListener(); 512 } 513 updateListener()514 private void updateListener() { 515 if (!mConfigured || mSensor == null) return; 516 if (mRequested && !mDisabled && !mRegistered) { 517 mRegistered = mSensors.requestTriggerSensor(this, mSensor); 518 if (DEBUG) Log.d(mTag, "requestTriggerSensor " + mRegistered); 519 } else if (mRegistered) { 520 final boolean rt = mSensors.cancelTriggerSensor(this, mSensor); 521 if (DEBUG) Log.d(mTag, "cancelTriggerSensor " + rt); 522 mRegistered = false; 523 } 524 } 525 526 @Override toString()527 public String toString() { 528 return new StringBuilder("{mRegistered=").append(mRegistered) 529 .append(", mRequested=").append(mRequested) 530 .append(", mDisabled=").append(mDisabled) 531 .append(", mConfigured=").append(mConfigured) 532 .append(", mDebugVibrate=").append(mDebugVibrate) 533 .append(", mSensor=").append(mSensor).append("}").toString(); 534 } 535 536 @Override onTrigger(TriggerEvent event)537 public void onTrigger(TriggerEvent event) { 538 mWakeLock.acquire(); 539 try { 540 if (DEBUG) Log.d(mTag, "onTrigger: " + triggerEventToString(event)); 541 if (mDebugVibrate) { 542 final Vibrator v = (Vibrator) mContext.getSystemService( 543 Context.VIBRATOR_SERVICE); 544 if (v != null) { 545 v.vibrate(1000, new AudioAttributes.Builder() 546 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) 547 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()); 548 } 549 } 550 551 mRegistered = false; 552 requestPulse(mPulseReason); 553 updateListener(); // reregister, this sensor only fires once 554 555 // reset the notification pulse schedule, but only if we think we were not triggered 556 // by a notification-related vibration 557 final long timeSinceNotification = System.currentTimeMillis() 558 - mNotificationPulseTime; 559 final boolean withinVibrationThreshold = 560 timeSinceNotification < mDozeParameters.getPickupVibrationThreshold(); 561 if (withinVibrationThreshold) { 562 if (DEBUG) Log.d(mTag, "Not resetting schedule, recent notification"); 563 } else { 564 resetNotificationResets(); 565 } 566 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 567 DozeLog.tracePickupPulse(withinVibrationThreshold); 568 } 569 } finally { 570 mWakeLock.release(); 571 } 572 } 573 } 574 575 private abstract class ProximityCheck implements SensorEventListener, Runnable { 576 private static final int TIMEOUT_DELAY_MS = 500; 577 578 protected static final int RESULT_UNKNOWN = 0; 579 protected static final int RESULT_NEAR = 1; 580 protected static final int RESULT_FAR = 2; 581 582 private final String mTag = DozeService.this.mTag + ".ProximityCheck"; 583 584 private boolean mRegistered; 585 private boolean mFinished; 586 private float mMaxRange; 587 588 abstract public void onProximityResult(int result); 589 590 public void check() { 591 if (mFinished || mRegistered) return; 592 final Sensor sensor = mSensors.getDefaultSensor(Sensor.TYPE_PROXIMITY); 593 if (sensor == null) { 594 if (DEBUG) Log.d(mTag, "No sensor found"); 595 finishWithResult(RESULT_UNKNOWN); 596 return; 597 } 598 // the pickup sensor interferes with the prox event, disable it until we have a result 599 mPickupSensor.setDisabled(true); 600 601 mMaxRange = sensor.getMaximumRange(); 602 mSensors.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0, mHandler); 603 mHandler.postDelayed(this, TIMEOUT_DELAY_MS); 604 mRegistered = true; 605 } 606 607 @Override 608 public void onSensorChanged(SensorEvent event) { 609 if (event.values.length == 0) { 610 if (DEBUG) Log.d(mTag, "Event has no values!"); 611 finishWithResult(RESULT_UNKNOWN); 612 } else { 613 if (DEBUG) Log.d(mTag, "Event: value=" + event.values[0] + " max=" + mMaxRange); 614 final boolean isNear = event.values[0] < mMaxRange; 615 finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR); 616 } 617 } 618 619 @Override 620 public void run() { 621 if (DEBUG) Log.d(mTag, "No event received before timeout"); 622 finishWithResult(RESULT_UNKNOWN); 623 } 624 625 private void finishWithResult(int result) { 626 if (mFinished) return; 627 if (mRegistered) { 628 mHandler.removeCallbacks(this); 629 mSensors.unregisterListener(this); 630 // we're done - reenable the pickup sensor 631 mPickupSensor.setDisabled(false); 632 mRegistered = false; 633 } 634 onProximityResult(result); 635 mFinished = true; 636 } 637 638 @Override 639 public void onAccuracyChanged(Sensor sensor, int accuracy) { 640 // noop 641 } 642 } 643 } 644