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