/* * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.cyanogenmod.platform.internal.display; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.Log; import java.io.PrintWriter; import java.util.Iterator; import java.util.LinkedList; public class AmbientLuxObserver { private static final String TAG = "AmbientLuxObserver"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Sensor mLightSensor; private final SensorManager mSensorManager; private final float mThresholdLux; private final int mThresholdDuration; private boolean mLightSensorEnabled = false; private int mLightSensorRate; private float mAmbientLux = 0.0f; private static final int LOW = 0; private static final int HIGH = 1; private int mState = LOW; private final AmbientLuxHandler mLuxHandler; private TransitionListener mCallback; private final TimedMovingAverageRingBuffer mRingBuffer; public interface TransitionListener { public void onTransition(int state, float ambientLux); } public AmbientLuxObserver(Context context, Looper looper, float thresholdLux, int thresholdDuration) { mLuxHandler = new AmbientLuxHandler(looper); mThresholdLux = thresholdLux; mThresholdDuration = thresholdDuration; mRingBuffer = new TimedMovingAverageRingBuffer(thresholdDuration); mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); mLightSensorRate = context.getResources().getInteger( com.android.internal.R.integer.config_autoBrightnessLightSensorRate); } private class AmbientLuxHandler extends Handler { private static final int MSG_UPDATE_LUX = 0; private static final int MSG_TRANSITION = 1; AmbientLuxHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { int direction = 0; float lux = 0.0f; synchronized (AmbientLuxObserver.this) { switch (msg.what) { case MSG_UPDATE_LUX: lux = (Float) msg.obj; mRingBuffer.add(lux); // FALL THRU case MSG_TRANSITION: mAmbientLux = mRingBuffer.getAverage(); if (DEBUG) { Log.d(TAG, "lux= " + lux + " mState=" + mState + " mAmbientLux=" + mAmbientLux); } direction = mAmbientLux >= mThresholdLux ? HIGH : LOW; if (mState != direction) { mState = direction; if (mCallback != null) { mCallback.onTransition(mState, mAmbientLux); } } // check again in case we didn't get any // more readings because the sensor settled if (mRingBuffer.size() > 1) { sendEmptyMessageDelayed(MSG_TRANSITION, mThresholdDuration / 2); } break; } } } void clear() { removeCallbacksAndMessages(null); } }; private final SensorEventListener mListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (mLightSensorEnabled) { Message.obtain(mLuxHandler, AmbientLuxHandler.MSG_UPDATE_LUX, event.values[0]).sendToTarget(); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // Not used. } }; public synchronized int getState() { return mState; } public synchronized void setTransitionListener(TransitionListener callback) { mCallback = callback; enableLightSensor(callback != null); } private void enableLightSensor(boolean enable) { if (enable && !mLightSensorEnabled) { mAmbientLux = 0.0f; mState = LOW; mLightSensorEnabled = true; mRingBuffer.clear(); mSensorManager.registerListener(mListener, mLightSensor, mLightSensorRate * 1000, mLuxHandler); } else if (!enable && mLightSensorEnabled) { mLightSensorEnabled = false; mSensorManager.unregisterListener(mListener); mLuxHandler.clear(); } } public void dump(PrintWriter pw) { pw.println(); pw.println(" AmbientLuxObserver State:"); pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); pw.println(" mState=" + mState); pw.println(" mAmbientLux=" + mAmbientLux); pw.println(" mRingBuffer=" + mRingBuffer.toString()); } /** * Calculates a simple moving average based on a fixed * duration sliding window. This is useful for dampening * erratic sensors and rolling thru transitional periods * smoothly. */ private static class TimedMovingAverageRingBuffer { private final LinkedList mRing = new LinkedList(); private final int mPeriod; private float mTotal = 0.0f; private static class Sample { public final long mTimestamp; public final float mValue; public Sample (long timestamp, float value) { mTimestamp = timestamp; mValue = value; } @Override public String toString() { return "(" + mValue + ", " + mTimestamp + ")"; } } public TimedMovingAverageRingBuffer(int period) { mPeriod = period; } public synchronized void add(float sample) { expire(); if (sample == 0.0f && mRing.size() == 0) { return; } mRing.offer(new Sample(System.currentTimeMillis(), sample)); mTotal += sample; } public synchronized int size() { return mRing.size(); } public synchronized float getAverage() { expire(); return mRing.size() == 0 ? 0.0f : (mTotal / mRing.size()); } public synchronized void clear() { mRing.clear(); mTotal = 0.0f; } private void expire() { long now = System.currentTimeMillis(); while (mRing.size() > 1 && ((now - mRing.peek().mTimestamp) > mPeriod)) { mTotal -= mRing.pop().mValue; } } @Override public synchronized String toString() { expire(); StringBuilder sb = new StringBuilder(); for (Iterator i = mRing.iterator(); i.hasNext();) { if (sb.length() > 0) { sb.append(", "); } sb.append(i.next()); } return "average=" + getAverage() + " length=" + mRing.size() + " mRing=[" + sb.toString() + "]"; } } }