Merge "Doze: Avoid pulsing in pockets." into lmp-dev
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 954046c..fcdbfc1 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -48,6 +48,8 @@
     private static SummaryStats sScreenOnPulsingStats;
     private static SummaryStats sScreenOnNotPulsingStats;
     private static SummaryStats sEmergencyCallStats;
+    private static SummaryStats sProxNearStats;
+    private static SummaryStats sProxFarStats;
 
     public static void tracePickupPulse(boolean withinVibrationThreshold) {
         if (!ENABLED) return;
@@ -88,6 +90,8 @@
                 sScreenOnPulsingStats = new SummaryStats();
                 sScreenOnNotPulsingStats = new SummaryStats();
                 sEmergencyCallStats = new SummaryStats();
+                sProxNearStats = new SummaryStats();
+                sProxFarStats = new SummaryStats();
                 log("init");
                 KeyguardUpdateMonitor.getInstance(context).registerCallback(sKeyguardCallback);
             }
@@ -133,6 +137,12 @@
         }
     }
 
+    public static void traceProximityResult(boolean near, long millis) {
+        if (!ENABLED) return;
+        log("proximityResult near=" + near + " millis=" + millis);
+        (near ? sProxNearStats : sProxFarStats).append();
+    }
+
     public static void dump(PrintWriter pw) {
         synchronized (DozeLog.class) {
             if (sMessages == null) return;
@@ -154,6 +164,8 @@
             sScreenOnPulsingStats.dump(pw, "Screen on (pulsing)");
             sScreenOnNotPulsingStats.dump(pw, "Screen on (not pulsing)");
             sEmergencyCallStats.dump(pw, "Emergency call");
+            sProxNearStats.dump(pw, "Proximity (near)");
+            sProxFarStats.dump(pw, "Proximity (far)");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 3afdc3d..f8c5e9c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -25,11 +25,15 @@
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
 import android.media.AudioAttributes;
+import android.os.Handler;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.os.Vibrator;
 import android.service.dreams.DreamService;
 import android.util.Log;
@@ -55,6 +59,7 @@
     private final String mTag = String.format(TAG + ".%08x", hashCode());
     private final Context mContext = this;
     private final DozeParameters mDozeParameters = new DozeParameters(mContext);
+    private final Handler mHandler = new Handler();
 
     private DozeHost mHost;
     private SensorManager mSensors;
@@ -197,33 +202,49 @@
             // Here we need a wakelock to stay awake until the pulse is finished.
             mWakeLock.acquire();
             mPulsing = true;
-            mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+            final long start = SystemClock.uptimeMillis();
+            new ProximityCheck() {
                 @Override
-                public void onPulseStarted() {
-                    if (mPulsing && mDreaming) {
-                        turnDisplayOn();
-                    }
-                }
-
-                @Override
-                public void onPulseFinished() {
-                    if (mPulsing && mDreaming) {
+                public void onProximityResult(int result) {
+                    // avoid pulsing in pockets
+                    final boolean isNear = result == RESULT_NEAR;
+                    DozeLog.traceProximityResult(isNear, SystemClock.uptimeMillis() - start);
+                    if (isNear) {
                         mPulsing = false;
-                        turnDisplayOff();
+                        mWakeLock.release();
+                        return;
                     }
-                    mWakeLock.release(); // needs to be unconditional to balance acquire
+
+                    // not in-pocket, continue pulsing
+                    mHost.pulseWhileDozing(new DozeHost.PulseCallback() {
+                        @Override
+                        public void onPulseStarted() {
+                            if (mPulsing && mDreaming) {
+                                turnDisplayOn();
+                            }
+                        }
+
+                        @Override
+                        public void onPulseFinished() {
+                            if (mPulsing && mDreaming) {
+                                mPulsing = false;
+                                turnDisplayOff();
+                            }
+                            mWakeLock.release(); // needs to be unconditional to balance acquire
+                        }
+                    });
                 }
-            });
+            }.check();
         }
     }
 
     private void turnDisplayOff() {
-        if (DEBUG) Log.d(TAG, "Display off");
+        if (DEBUG) Log.d(mTag, "Display off");
         setDozeScreenState(Display.STATE_OFF);
     }
 
     private void turnDisplayOn() {
-        if (DEBUG) Log.d(TAG, "Display on");
+        if (DEBUG) Log.d(mTag, "Display on");
         setDozeScreenState(mDisplayStateSupported ? Display.STATE_DOZE : Display.STATE_ON);
     }
 
@@ -270,24 +291,24 @@
     }
 
     private void resetNotificationResets() {
-        if (DEBUG) Log.d(TAG, "resetNotificationResets");
+        if (DEBUG) Log.d(mTag, "resetNotificationResets");
         mScheduleResetsRemaining = mDozeParameters.getPulseScheduleResets();
     }
 
     private void updateNotificationPulse() {
-        if (DEBUG) Log.d(TAG, "updateNotificationPulse");
+        if (DEBUG) Log.d(mTag, "updateNotificationPulse");
         if (!mDozeParameters.getPulseOnNotifications()) return;
         if (mScheduleResetsRemaining <= 0) {
-            if (DEBUG) Log.d(TAG, "No more schedule resets remaining");
+            if (DEBUG) Log.d(mTag, "No more schedule resets remaining");
             return;
         }
         final long now = System.currentTimeMillis();
         if ((now - mNotificationPulseTime) < mDozeParameters.getPulseDuration()) {
-            if (DEBUG) Log.d(TAG, "Recently updated, not resetting schedule");
+            if (DEBUG) Log.d(mTag, "Recently updated, not resetting schedule");
             return;
         }
         mScheduleResetsRemaining--;
-        if (DEBUG) Log.d(TAG, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
+        if (DEBUG) Log.d(mTag, "mScheduleResetsRemaining = " + mScheduleResetsRemaining);
         mNotificationPulseTime = now;
         rescheduleNotificationPulse(true /*predicate*/);
     }
@@ -302,31 +323,31 @@
     }
 
     private void rescheduleNotificationPulse(boolean predicate) {
-        if (DEBUG) Log.d(TAG, "rescheduleNotificationPulse predicate=" + predicate);
+        if (DEBUG) Log.d(mTag, "rescheduleNotificationPulse predicate=" + predicate);
         final PendingIntent notificationPulseIntent = notificationPulseIntent(0);
         mAlarmManager.cancel(notificationPulseIntent);
         if (!predicate) {
-            if (DEBUG) Log.d(TAG, "  don't reschedule: predicate is false");
+            if (DEBUG) Log.d(mTag, "  don't reschedule: predicate is false");
             return;
         }
         final PulseSchedule schedule = mDozeParameters.getPulseSchedule();
         if (schedule == null) {
-            if (DEBUG) Log.d(TAG, "  don't reschedule: schedule is null");
+            if (DEBUG) Log.d(mTag, "  don't reschedule: schedule is null");
             return;
         }
         final long now = System.currentTimeMillis();
         final long time = schedule.getNextTime(now, mNotificationPulseTime);
         if (time <= 0) {
-            if (DEBUG) Log.d(TAG, "  don't reschedule: time is " + time);
+            if (DEBUG) Log.d(mTag, "  don't reschedule: time is " + time);
             return;
         }
         final long delta = time - now;
         if (delta <= 0) {
-            if (DEBUG) Log.d(TAG, "  don't reschedule: delta is " + delta);
+            if (DEBUG) Log.d(mTag, "  don't reschedule: delta is " + delta);
             return;
         }
         final long instance = time - mNotificationPulseTime;
-        if (DEBUG) Log.d(TAG, "Scheduling pulse " + instance + " in " + delta + "ms for "
+        if (DEBUG) Log.d(mTag, "Scheduling pulse " + instance + " in " + delta + "ms for "
                 + new Date(time));
         mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, time, notificationPulseIntent(instance));
     }
@@ -404,7 +425,9 @@
         private final boolean mConfigured;
         private final boolean mDebugVibrate;
 
-        private boolean mEnabled;
+        private boolean mRequested;
+        private boolean mRegistered;
+        private boolean mDisabled;
 
         public TriggerSensor(int type, boolean configured, boolean debugVibrate) {
             mSensor = mSensors.getDefaultSensor(type);
@@ -413,19 +436,34 @@
         }
 
         public void setListening(boolean listen) {
+            if (mRequested == listen) return;
+            mRequested = listen;
+            updateListener();
+        }
+
+        public void setDisabled(boolean disabled) {
+            if (mDisabled == disabled) return;
+            mDisabled = disabled;
+            updateListener();
+        }
+
+        private void updateListener() {
             if (!mConfigured || mSensor == null) return;
-            if (listen) {
-                mEnabled = mSensors.requestTriggerSensor(this, mSensor);
-            } else if (mEnabled) {
+            if (mRequested && !mDisabled) {
+                mRegistered = mSensors.requestTriggerSensor(this, mSensor);
+            } else if (mRegistered) {
                 mSensors.cancelTriggerSensor(this, mSensor);
-                mEnabled = false;
+                mRegistered = false;
             }
         }
 
         @Override
         public String toString() {
-            return new StringBuilder("{mEnabled=").append(mEnabled).append(", mConfigured=")
-                    .append(mConfigured).append(", mDebugVibrate=").append(mDebugVibrate)
+            return new StringBuilder("{mRegistered=").append(mRegistered)
+                    .append(", mRequested=").append(mRequested)
+                    .append(", mDisabled=").append(mDisabled)
+                    .append(", mConfigured=").append(mConfigured)
+                    .append(", mDebugVibrate=").append(mDebugVibrate)
                     .append(", mSensor=").append(mSensor).append("}").toString();
         }
 
@@ -449,7 +487,8 @@
 
                 // reset the notification pulse schedule, but only if we think we were not triggered
                 // by a notification-related vibration
-                final long timeSinceNotification = System.currentTimeMillis() - mNotificationPulseTime;
+                final long timeSinceNotification = System.currentTimeMillis()
+                        - mNotificationPulseTime;
                 final boolean withinVibrationThreshold =
                         timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
                 if (withinVibrationThreshold) {
@@ -465,4 +504,73 @@
             }
         }
     }
+
+    private abstract class ProximityCheck implements SensorEventListener, Runnable {
+        private static final int TIMEOUT_DELAY_MS = 500;
+
+        protected static final int RESULT_UNKNOWN = 0;
+        protected static final int RESULT_NEAR = 1;
+        protected static final int RESULT_FAR = 2;
+
+        private final String mTag = DozeService.this.mTag + ".ProximityCheck";
+
+        private boolean mRegistered;
+        private boolean mFinished;
+        private float mMaxRange;
+
+        abstract public void onProximityResult(int result);
+
+        public void check() {
+            if (mFinished || mRegistered) return;
+            final Sensor sensor = mSensors.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+            if (sensor == null) {
+                if (DEBUG) Log.d(mTag, "No sensor found");
+                finishWithResult(RESULT_UNKNOWN);
+                return;
+            }
+            // the pickup sensor interferes with the prox event, disable it until we have a result
+            mPickupSensor.setDisabled(true);
+
+            mMaxRange = sensor.getMaximumRange();
+            mSensors.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0, mHandler);
+            mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
+            mRegistered = true;
+        }
+
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (event.values.length == 0) {
+                if (DEBUG) Log.d(mTag, "Event has no values!");
+                finishWithResult(RESULT_UNKNOWN);
+            } else {
+                if (DEBUG) Log.d(mTag, "Event: value=" + event.values[0] + " max=" + mMaxRange);
+                final boolean isNear = event.values[0] < mMaxRange;
+                finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
+            }
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG) Log.d(mTag, "No event received before timeout");
+            finishWithResult(RESULT_UNKNOWN);
+        }
+
+        private void finishWithResult(int result) {
+            if (mFinished) return;
+            if (mRegistered) {
+                mHandler.removeCallbacks(this);
+                mSensors.unregisterListener(this);
+                // we're done - reenable the pickup sensor
+                mPickupSensor.setDisabled(false);
+                mRegistered = false;
+            }
+            onProximityResult(result);
+            mFinished = true;
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // noop
+        }
+    }
 }