Home | History | Annotate | Download | only in doze
      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.ActivityManager;
     20 import android.app.UiModeManager;
     21 import android.content.BroadcastReceiver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentFilter;
     25 import android.content.res.Configuration;
     26 import android.database.ContentObserver;
     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.net.Uri;
     35 import android.os.Handler;
     36 import android.os.PowerManager;
     37 import android.os.SystemClock;
     38 import android.os.UserHandle;
     39 import android.os.Vibrator;
     40 import android.provider.Settings;
     41 import android.service.dreams.DreamService;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 import android.view.Display;
     45 
     46 import com.android.internal.hardware.AmbientDisplayConfiguration;
     47 import com.android.internal.logging.MetricsLogger;
     48 import com.android.internal.logging.MetricsProto.MetricsEvent;
     49 import com.android.systemui.SystemUIApplication;
     50 import com.android.systemui.statusbar.phone.DozeParameters;
     51 
     52 import java.io.FileDescriptor;
     53 import java.io.PrintWriter;
     54 import java.util.Date;
     55 import java.util.List;
     56 
     57 public class DozeService extends DreamService {
     58     private static final String TAG = "DozeService";
     59     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     60 
     61     private static final String ACTION_BASE = "com.android.systemui.doze";
     62     private static final String PULSE_ACTION = ACTION_BASE + ".pulse";
     63 
     64     /**
     65      * If true, reregisters all trigger sensors when the screen turns off.
     66      */
     67     private static final boolean REREGISTER_ALL_SENSORS_ON_SCREEN_OFF = true;
     68 
     69     private final String mTag = String.format(TAG + ".%08x", hashCode());
     70     private final Context mContext = this;
     71     private final DozeParameters mDozeParameters = new DozeParameters(mContext);
     72     private final Handler mHandler = new Handler();
     73 
     74     private DozeHost mHost;
     75     private SensorManager mSensorManager;
     76     private TriggerSensor[] mSensors;
     77     private TriggerSensor mPickupSensor;
     78     private PowerManager mPowerManager;
     79     private PowerManager.WakeLock mWakeLock;
     80     private UiModeManager mUiModeManager;
     81     private boolean mDreaming;
     82     private boolean mPulsing;
     83     private boolean mBroadcastReceiverRegistered;
     84     private boolean mDisplayStateSupported;
     85     private boolean mPowerSaveActive;
     86     private boolean mCarMode;
     87     private long mNotificationPulseTime;
     88 
     89     private AmbientDisplayConfiguration mConfig;
     90 
     91     public DozeService() {
     92         if (DEBUG) Log.d(mTag, "new DozeService()");
     93         setDebug(DEBUG);
     94     }
     95 
     96     @Override
     97     protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
     98         super.dumpOnHandler(fd, pw, args);
     99         pw.print("  mDreaming: "); pw.println(mDreaming);
    100         pw.print("  mPulsing: "); pw.println(mPulsing);
    101         pw.print("  mWakeLock: held="); pw.println(mWakeLock.isHeld());
    102         pw.print("  mHost: "); pw.println(mHost);
    103         pw.print("  mBroadcastReceiverRegistered: "); pw.println(mBroadcastReceiverRegistered);
    104         for (TriggerSensor s : mSensors) {
    105             pw.print("  sensor: ");
    106             pw.println(s);
    107         }
    108         pw.print("  mDisplayStateSupported: "); pw.println(mDisplayStateSupported);
    109         pw.print("  mPowerSaveActive: "); pw.println(mPowerSaveActive);
    110         pw.print("  mCarMode: "); pw.println(mCarMode);
    111         pw.print("  mNotificationPulseTime: "); pw.println(
    112                 DozeLog.FORMAT.format(new Date(mNotificationPulseTime
    113                         - SystemClock.elapsedRealtime() + System.currentTimeMillis())));
    114         mDozeParameters.dump(pw);
    115     }
    116 
    117     @Override
    118     public void onCreate() {
    119         if (DEBUG) Log.d(mTag, "onCreate");
    120         super.onCreate();
    121 
    122         if (getApplication() instanceof SystemUIApplication) {
    123             final SystemUIApplication app = (SystemUIApplication) getApplication();
    124             mHost = app.getComponent(DozeHost.class);
    125         }
    126         if (mHost == null) Log.w(TAG, "No doze service host found.");
    127 
    128         setWindowless(true);
    129 
    130         mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
    131         mConfig = new AmbientDisplayConfiguration(mContext);
    132         mSensors = new TriggerSensor[] {
    133                 new TriggerSensor(
    134                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
    135                         null /* setting */,
    136                         mDozeParameters.getPulseOnSigMotion(),
    137                         mDozeParameters.getVibrateOnSigMotion(),
    138                         DozeLog.PULSE_REASON_SENSOR_SIGMOTION),
    139                 mPickupSensor = new TriggerSensor(
    140                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
    141                         Settings.Secure.DOZE_PULSE_ON_PICK_UP,
    142                         mConfig.pulseOnPickupAvailable(), mDozeParameters.getVibrateOnPickup(),
    143                         DozeLog.PULSE_REASON_SENSOR_PICKUP),
    144                 new TriggerSensor(
    145                         findSensorWithType(mConfig.doubleTapSensorType()),
    146                         Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, true,
    147                         mDozeParameters.getVibrateOnPickup(),
    148                         DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP)
    149         };
    150         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
    151         mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    152         mWakeLock.setReferenceCounted(true);
    153         mDisplayStateSupported = mDozeParameters.getDisplayStateSupported();
    154         mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
    155         turnDisplayOff();
    156     }
    157 
    158     @Override
    159     public void onAttachedToWindow() {
    160         if (DEBUG) Log.d(mTag, "onAttachedToWindow");
    161         super.onAttachedToWindow();
    162     }
    163 
    164     @Override
    165     public void onDreamingStarted() {
    166         super.onDreamingStarted();
    167 
    168         if (mHost == null) {
    169             finish();
    170             return;
    171         }
    172 
    173         mPowerSaveActive = mHost.isPowerSaveActive();
    174         mCarMode = mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
    175         if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze() + " mPowerSaveActive="
    176                 + mPowerSaveActive + " mCarMode=" + mCarMode);
    177         if (mPowerSaveActive) {
    178             finishToSavePower();
    179             return;
    180         }
    181         if (mCarMode) {
    182             finishForCarMode();
    183             return;
    184         }
    185 
    186         mDreaming = true;
    187         listenForPulseSignals(true);
    188 
    189         // Ask the host to get things ready to start dozing.
    190         // Once ready, we call startDozing() at which point the CPU may suspend
    191         // and we will need to acquire a wakelock to do work.
    192         mHost.startDozing(mWakeLock.wrap(() -> {
    193             if (mDreaming) {
    194                 startDozing();
    195 
    196                 // From this point until onDreamingStopped we will need to hold a
    197                 // wakelock whenever we are doing work.  Note that we never call
    198                 // stopDozing because can we just keep dozing until the bitter end.
    199             }
    200         }));
    201     }
    202 
    203     @Override
    204     public void onDreamingStopped() {
    205         if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing());
    206         super.onDreamingStopped();
    207 
    208         if (mHost == null) {
    209             return;
    210         }
    211 
    212         mDreaming = false;
    213         listenForPulseSignals(false);
    214 
    215         // Tell the host that it's over.
    216         mHost.stopDozing();
    217     }
    218 
    219     private void requestPulse(final int reason) {
    220         requestPulse(reason, false /* performedProxCheck */);
    221     }
    222 
    223     private void requestPulse(final int reason, boolean performedProxCheck) {
    224         if (mHost != null && mDreaming && !mPulsing) {
    225             // Let the host know we want to pulse.  Wait for it to be ready, then
    226             // turn the screen on.  When finished, turn the screen off again.
    227             // Here we need a wakelock to stay awake until the pulse is finished.
    228             mWakeLock.acquire();
    229             mPulsing = true;
    230             if (!mDozeParameters.getProxCheckBeforePulse()) {
    231                 // skip proximity check
    232                 continuePulsing(reason);
    233                 return;
    234             }
    235             final long start = SystemClock.uptimeMillis();
    236             if (performedProxCheck) {
    237                 // the caller already performed a successful proximity check; we'll only do one to
    238                 // capture statistics, continue pulsing immediately.
    239                 continuePulsing(reason);
    240             }
    241             // perform a proximity check
    242             new ProximityCheck() {
    243                 @Override
    244                 public void onProximityResult(int result) {
    245                     final boolean isNear = result == RESULT_NEAR;
    246                     final long end = SystemClock.uptimeMillis();
    247                     DozeLog.traceProximityResult(mContext, isNear, end - start, reason);
    248                     if (performedProxCheck) {
    249                         // we already continued
    250                         return;
    251                     }
    252                     // avoid pulsing in pockets
    253                     if (isNear) {
    254                         mPulsing = false;
    255                         mWakeLock.release();
    256                         return;
    257                     }
    258 
    259                     // not in-pocket, continue pulsing
    260                     continuePulsing(reason);
    261                 }
    262             }.check();
    263         }
    264     }
    265 
    266     private void continuePulsing(int reason) {
    267         if (mHost.isPulsingBlocked()) {
    268             mPulsing = false;
    269             mWakeLock.release();
    270             return;
    271         }
    272         mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
    273             @Override
    274             public void onPulseStarted() {
    275                 if (mPulsing && mDreaming) {
    276                     turnDisplayOn();
    277                 }
    278             }
    279 
    280             @Override
    281             public void onPulseFinished() {
    282                 if (mPulsing && mDreaming) {
    283                     mPulsing = false;
    284                     if (REREGISTER_ALL_SENSORS_ON_SCREEN_OFF) {
    285                         reregisterAllSensors();
    286                     }
    287                     turnDisplayOff();
    288                 }
    289                 mWakeLock.release(); // needs to be unconditional to balance acquire
    290             }
    291         }, reason);
    292     }
    293 
    294     private void turnDisplayOff() {
    295         if (DEBUG) Log.d(mTag, "Display off");
    296         setDozeScreenState(Display.STATE_OFF);
    297     }
    298 
    299     private void turnDisplayOn() {
    300         if (DEBUG) Log.d(mTag, "Display on");
    301         setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON);
    302     }
    303 
    304     private void finishToSavePower() {
    305         Log.w(mTag, "Exiting ambient mode due to low power battery saver");
    306         finish();
    307     }
    308 
    309     private void finishForCarMode() {
    310         Log.w(mTag, "Exiting ambient mode, not allowed in car mode");
    311         finish();
    312     }
    313 
    314     private void listenForPulseSignals(boolean listen) {
    315         if (DEBUG) Log.d(mTag, "listenForPulseSignals: " + listen);
    316         for (TriggerSensor s : mSensors) {
    317             s.setListening(listen);
    318         }
    319         listenForBroadcasts(listen);
    320         listenForNotifications(listen);
    321     }
    322 
    323     private void reregisterAllSensors() {
    324         for (TriggerSensor s : mSensors) {
    325             s.setListening(false);
    326         }
    327         for (TriggerSensor s : mSensors) {
    328             s.setListening(true);
    329         }
    330     }
    331 
    332     private void listenForBroadcasts(boolean listen) {
    333         if (listen) {
    334             final IntentFilter filter = new IntentFilter(PULSE_ACTION);
    335             filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
    336             filter.addAction(Intent.ACTION_USER_SWITCHED);
    337             mContext.registerReceiver(mBroadcastReceiver, filter);
    338 
    339             for (TriggerSensor s : mSensors) {
    340                 if (s.mConfigured && !TextUtils.isEmpty(s.mSetting)) {
    341                     mContext.getContentResolver().registerContentObserver(
    342                             Settings.Secure.getUriFor(s.mSetting), false /* descendants */,
    343                             mSettingsObserver, UserHandle.USER_ALL);
    344                 }
    345             }
    346             mBroadcastReceiverRegistered = true;
    347         } else {
    348             if (mBroadcastReceiverRegistered) {
    349                 mContext.unregisterReceiver(mBroadcastReceiver);
    350                 mContext.getContentResolver().unregisterContentObserver(mSettingsObserver);
    351             }
    352             mBroadcastReceiverRegistered = false;
    353         }
    354     }
    355 
    356     private void listenForNotifications(boolean listen) {
    357         if (listen) {
    358             mHost.addCallback(mHostCallback);
    359         } else {
    360             mHost.removeCallback(mHostCallback);
    361         }
    362     }
    363 
    364     private void requestNotificationPulse() {
    365         if (DEBUG) Log.d(mTag, "requestNotificationPulse");
    366         if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return;
    367         mNotificationPulseTime = SystemClock.elapsedRealtime();
    368         requestPulse(DozeLog.PULSE_REASON_NOTIFICATION);
    369     }
    370 
    371     private static String triggerEventToString(TriggerEvent event) {
    372         if (event == null) return null;
    373         final StringBuilder sb = new StringBuilder("TriggerEvent[")
    374                 .append(event.timestamp).append(',')
    375                 .append(event.sensor.getName());
    376         if (event.values != null) {
    377             for (int i = 0; i < event.values.length; i++) {
    378                 sb.append(',').append(event.values[i]);
    379             }
    380         }
    381         return sb.append(']').toString();
    382     }
    383 
    384     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
    385         @Override
    386         public void onReceive(Context context, Intent intent) {
    387             if (PULSE_ACTION.equals(intent.getAction())) {
    388                 if (DEBUG) Log.d(mTag, "Received pulse intent");
    389                 requestPulse(DozeLog.PULSE_REASON_INTENT);
    390             }
    391             if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
    392                 mCarMode = true;
    393                 if (mCarMode && mDreaming) {
    394                     finishForCarMode();
    395                 }
    396             }
    397             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
    398                 for (TriggerSensor s : mSensors) {
    399                     s.updateListener();
    400                 }
    401             }
    402         }
    403     };
    404 
    405     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
    406         @Override
    407         public void onChange(boolean selfChange, Uri uri, int userId) {
    408             if (userId != ActivityManager.getCurrentUser()) {
    409                 return;
    410             }
    411             for (TriggerSensor s : mSensors) {
    412                 s.updateListener();
    413             }
    414         }
    415     };
    416 
    417     private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
    418         @Override
    419         public void onNewNotifications() {
    420             if (DEBUG) Log.d(mTag, "onNewNotifications (noop)");
    421             // noop for now
    422         }
    423 
    424         @Override
    425         public void onBuzzBeepBlinked() {
    426             if (DEBUG) Log.d(mTag, "onBuzzBeepBlinked");
    427             requestNotificationPulse();
    428         }
    429 
    430         @Override
    431         public void onNotificationLight(boolean on) {
    432             if (DEBUG) Log.d(mTag, "onNotificationLight (noop) on=" + on);
    433             // noop for now
    434         }
    435 
    436         @Override
    437         public void onPowerSaveChanged(boolean active) {
    438             mPowerSaveActive = active;
    439             if (mPowerSaveActive && mDreaming) {
    440                 finishToSavePower();
    441             }
    442         }
    443     };
    444 
    445     private Sensor findSensorWithType(String type) {
    446         if (TextUtils.isEmpty(type)) {
    447             return null;
    448         }
    449         List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
    450         for (Sensor s : sensorList) {
    451             if (type.equals(s.getStringType())) {
    452                 return s;
    453             }
    454         }
    455         return null;
    456     }
    457 
    458     private class TriggerSensor extends TriggerEventListener {
    459         final Sensor mSensor;
    460         final boolean mConfigured;
    461         final boolean mDebugVibrate;
    462         final int mPulseReason;
    463         final String mSetting;
    464 
    465         private boolean mRequested;
    466         private boolean mRegistered;
    467         private boolean mDisabled;
    468 
    469         public TriggerSensor(Sensor sensor, String setting, boolean configured,
    470                 boolean debugVibrate, int pulseReason) {
    471             mSensor = sensor;
    472             mSetting = setting;
    473             mConfigured = configured;
    474             mDebugVibrate = debugVibrate;
    475             mPulseReason = pulseReason;
    476         }
    477 
    478         public void setListening(boolean listen) {
    479             if (mRequested == listen) return;
    480             mRequested = listen;
    481             updateListener();
    482         }
    483 
    484         public void setDisabled(boolean disabled) {
    485             if (mDisabled == disabled) return;
    486             mDisabled = disabled;
    487             updateListener();
    488         }
    489 
    490         public void updateListener() {
    491             if (!mConfigured || mSensor == null) return;
    492             if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
    493                 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
    494                 if (DEBUG) Log.d(mTag, "requestTriggerSensor " + mRegistered);
    495             } else if (mRegistered) {
    496                 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
    497                 if (DEBUG) Log.d(mTag, "cancelTriggerSensor " + rt);
    498                 mRegistered = false;
    499             }
    500         }
    501 
    502         private boolean enabledBySetting() {
    503             if (TextUtils.isEmpty(mSetting)) {
    504                 return true;
    505             }
    506             return Settings.Secure.getIntForUser(mContext.getContentResolver(), mSetting, 1,
    507                     UserHandle.USER_CURRENT) != 0;
    508         }
    509 
    510         @Override
    511         public String toString() {
    512             return new StringBuilder("{mRegistered=").append(mRegistered)
    513                     .append(", mRequested=").append(mRequested)
    514                     .append(", mDisabled=").append(mDisabled)
    515                     .append(", mConfigured=").append(mConfigured)
    516                     .append(", mDebugVibrate=").append(mDebugVibrate)
    517                     .append(", mSensor=").append(mSensor).append("}").toString();
    518         }
    519 
    520         @Override
    521         public void onTrigger(TriggerEvent event) {
    522             mWakeLock.acquire();
    523             try {
    524                 if (DEBUG) Log.d(mTag, "onTrigger: " + triggerEventToString(event));
    525                 boolean sensorPerformsProxCheck = false;
    526                 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
    527                     int subType = (int) event.values[0];
    528                     MetricsLogger.action(mContext, MetricsEvent.ACTION_AMBIENT_GESTURE, subType);
    529                     sensorPerformsProxCheck = mDozeParameters.getPickupSubtypePerformsProxCheck(
    530                             subType);
    531                 }
    532                 if (mDebugVibrate) {
    533                     final Vibrator v = (Vibrator) mContext.getSystemService(
    534                             Context.VIBRATOR_SERVICE);
    535                     if (v != null) {
    536                         v.vibrate(1000, new AudioAttributes.Builder()
    537                                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
    538                                 .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build());
    539                     }
    540                 }
    541 
    542                 mRegistered = false;
    543                 requestPulse(mPulseReason, sensorPerformsProxCheck);
    544                 updateListener();  // reregister, this sensor only fires once
    545 
    546                 // record pickup gesture, also keep track of whether we might have been triggered
    547                 // by recent vibration.
    548                 final long timeSinceNotification = SystemClock.elapsedRealtime()
    549                         - mNotificationPulseTime;
    550                 final boolean withinVibrationThreshold =
    551                         timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
    552                 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
    553                     DozeLog.tracePickupPulse(mContext, 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 = mSensorManager.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             mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
    589                     mHandler);
    590             mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
    591             mRegistered = true;
    592         }
    593 
    594         @Override
    595         public void onSensorChanged(SensorEvent event) {
    596             if (event.values.length == 0) {
    597                 if (DEBUG) Log.d(mTag, "Event has no values!");
    598                 finishWithResult(RESULT_UNKNOWN);
    599             } else {
    600                 if (DEBUG) Log.d(mTag, "Event: value=" + event.values[0] + " max=" + mMaxRange);
    601                 final boolean isNear = event.values[0] < mMaxRange;
    602                 finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
    603             }
    604         }
    605 
    606         @Override
    607         public void run() {
    608             if (DEBUG) Log.d(mTag, "No event received before timeout");
    609             finishWithResult(RESULT_UNKNOWN);
    610         }
    611 
    612         private void finishWithResult(int result) {
    613             if (mFinished) return;
    614             if (mRegistered) {
    615                 mHandler.removeCallbacks(this);
    616                 mSensorManager.unregisterListener(this);
    617                 // we're done - reenable the pickup sensor
    618                 mPickupSensor.setDisabled(false);
    619                 mRegistered = false;
    620             }
    621             onProximityResult(result);
    622             mFinished = true;
    623         }
    624 
    625         @Override
    626         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    627             // noop
    628         }
    629     }
    630 }
    631