📜 ⬆️ ⬇️

How to distinguish day from night if you are Android

Hi, Habr.


Today we will talk about how great it is to read in the dark. In childhood, all of us were forbidden to mothers from doing this, but now there are tablets! Unlike paper books, they do not need to shine a flashlight, they will do everything for you. And that is what we teach them. But first things first.




In one of the mobile applications for Android, which we developed, there is a screen for reading news. For the convenience of users, we have provided in it two display modes - day and night. It's simple: if the device “knows” what the day is (or just light), the usual screen works, with black font on white. If it understands that the user is in the dark, he suggests that he switch to night mode — a white font and a black screen.
')


The most important thing is to prompt the user to make the switch. For this, it is necessary to determine whether the day is now or the night using the device sensor.

Working with any sensor in Android comes down to the following steps:
1. Access the SensorManager .
2. Get access to the desired sensor.
3. Register a listener using a common interface for all sensors.

An example of working with SensorManager:

public class SensorActivity extends Activity implements SensorEventListener { private final SensorManager sensorManager; private final Sensor lightSensor; public SensorActivity() { sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //  } protected void onResume() { super.onResume(); sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); //   } protected void onPause() { super.onPause(); sensorManager.unregisterListener(this); //   } public void onAccuracyChanged(Sensor sensor, int accuracy) { } public void onSensorChanged(SensorEvent event) { //   SensorEvent } } 


All data from the sensor comes in the array SensorEvent # values .
According to the documentation, this is what the light sensor sends us:

Sensor.TYPE_LIGHT:
values ​​[0]: Ambient light level in SI lux units

Only one value - the number of suites.

Minute of education


What is a suite? Well, everything is simple here: a lux is a unit of illumination of a surface of 1m² with a luminous flux of radiation falling on it equal to 1 lm (lumen). A lumen is a unit of luminous flux, equal to the luminous flux emitted by a point-like isotropic source, with a luminous intensity equal to one candela, in a solid angle of one steradian. And steradian is ... But let's just look at the picture:



(source blog.tredz.co.uk/wp-content/uploads/2012/09/light-dia1.jpg )
If everything is together, then a luxury is such an illumination of a surface of 1 m², which arises when it is illuminated with such a bulb with a luminous intensity of 1 cd (candela) with such a beam of light the size of 1 steradian.
OK, the number of suites we know, what next? Next, we will try to find out what level of illumination is typical for daylight and for dark.
By the way, do not try to search in search engines for the keywords "luxury", "day", "night", if you do not want to be aware of the best prices for comfortable hotel rooms :).

In the Russian Wiki, you can find a table with examples of illumination , in which you can find such useful examples as:
• up to 20 - In the sea at a depth of ~ 50 m.
• 350 ± 150 - Sunrise or Sunset on Venus



Due to the fact that we are making the application not for the inhabitants of Venus, we will dwell on the value of 50 suites, which corresponds to the light in the living room.

The matter of technology


Let's write the LightSensorManager class, which can be “turned on” and “off” and which will report to us if it has become dark or light.

Lightsensormanager
 public class LightSensorManager implements SensorEventListener { private enum Environment {DAY, NIGHT} public interface EnvironmentChangedListener { void onDayDetected(); void onNightDetected(); } private static final int THRESHOLD_LUX = 50; private static final String TAG = "LightSensorManager"; private final SensorManager sensorManager; private final Sensor lightSensor; private EnvironmentChangedListener environmentChangedListener; private Environment currentEnvironment; public LightSensorManager(Context context) { sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //   } public void enable() { if (lightSensor != null){ sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); } else { Log.w(TAG, "Light sensor in not supported"); } } public void disable() { sensorManager.unregisterListener(this); } public EnvironmentChangedListener getEnvironmentChangedListener() { return environmentChangedListener; } public void setEnvironmentChangedListener(EnvironmentChangedListener environmentChangedListener) { this.environmentChangedListener = environmentChangedListener; } @Override public void onSensorChanged(SensorEvent event) { float luxLevel = event.values[0]; Environment oldEnvironment = currentEnvironment; currentEnvironment = luxLevel < THRESHOLD_LUX ? Environment.NIGHT : Environment.DAY; if (hasChanged(oldEnvironment, currentEnvironment)){ callListener(currentEnvironment); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} private boolean hasChanged(Environment oldEnvironment, Environment newEnvironment) { return oldEnvironment != newEnvironment; } private void callListener(Environment environment) { if (environmentChangedListener == null || environment == null){ return; } switch (environment) { case DAY: environmentChangedListener.onDayDetected(); break; case NIGHT: environmentChangedListener.onNightDetected(); break; } } } 



Now we can add this manager to our Activity, including it in onResume and off in onPause.
You can watch how the light level changes without leaving the room. Just find the sensor on the device and close it with your finger.
It may happen that the device is in a room with an illumination level approximately equal to our chosen threshold value of 50 lux and, fluctuating, will often cross the threshold value. This will lead to the fact that our manager will begin very often to inform us about the change of day and night. We will get rid of this by entering 2 threshold values: upper and lower. Above the top we will be considered daytime, below the bottom one - at night, and the changes between the thresholds will be ignored.

LightSensorManager with two thresholds
 public class LightSensorManager implements SensorEventListener { private enum Environment {DAY, NIGHT} public interface EnvironmentChangedListener { void onDayDetected(); void onNightDetected(); } private static final int THRESHOLD_DAY_LUX = 50; private static final int THRESHOLD_NIGHT_LUX = 40; private static final String TAG = "LightSensorManager"; private final SensorManager sensorManager; private final Sensor lightSensor; private EnvironmentChangedListener environmentChangedListener; private Environment currentEnvironment; public LightSensorManager(Context context) { sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); //   } public void enable() { if (lightSensor != null){ sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); } else { Log.w(TAG, "Light sensor in not supported"); } } public void disable() { sensorManager.unregisterListener(this); } public EnvironmentChangedListener getEnvironmentChangedListener() { return environmentChangedListener; } public void setEnvironmentChangedListener(EnvironmentChangedListener environmentChangedListener) { this.environmentChangedListener = environmentChangedListener; } @Override public void onSensorChanged(SensorEvent event) { float luxLevel = event.values[0]; Environment oldEnvironment = currentEnvironment; if (luxLevel < THRESHOLD_NIGHT_LUX){ currentEnvironment = Environment.NIGHT; } else if (luxLevel > THRESHOLD_DAY_LUX){ currentEnvironment = Environment.DAY; } if (hasChanged(oldEnvironment, currentEnvironment)){ callListener(currentEnvironment); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} private boolean hasChanged(Environment oldEnvironment, Environment newEnvironment) { return oldEnvironment != newEnvironment; } private void callListener(Environment environment) { if (environmentChangedListener == null || environment == null){ return; } switch (environment) { case DAY: environmentChangedListener.onDayDetected(); break; case NIGHT: environmentChangedListener.onNightDetected(); break; } } } 



And one more nuance: we can get a false positive with a short-term strong change in the level of illumination. For example, if the light “blinks” due to a voltage drop or the user will pass at night under a lamppost.



We can get rid of this problem if we program a low pass filter (aka low pass filter). It will smooth out all the sharp and short-term changes in the data from the sensor.

LightSensorManager with low pass filter
 public class LightSensorManager implements SensorEventListener { private enum Environment {DAY, NIGHT} public interface EnvironmentChangedListener { void onDayDetected(); void onNightDetected(); } private static final float SMOOTHING = 10; private static final int THRESHOLD_DAY_LUX = 50; private static final int THRESHOLD_NIGHT_LUX = 40; private static final String TAG = "LightSensorManager"; private final SensorManager sensorManager; private final Sensor lightSensor; private EnvironmentChangedListener environmentChangedListener; private Environment currentEnvironment; private final LowPassFilter lowPassFilter; public LightSensorManager(Context context) { sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); lowPassFilter = new LowPassFilter(SMOOTHING); } public void enable() { if (lightSensor != null){ sensorManager.registerListener(this, lightSensor, SensorManager.SENSOR_DELAY_NORMAL); } else { Log.w(TAG, "Light sensor in not supported"); } } public void disable() { sensorManager.unregisterListener(this); } public EnvironmentChangedListener getEnvironmentChangedListener() { return environmentChangedListener; } public void setEnvironmentChangedListener(EnvironmentChangedListener environmentChangedListener) { this.environmentChangedListener = environmentChangedListener; } @Override public void onSensorChanged(SensorEvent event) { float luxLevel = event.values[0]; luxLevel = lowPassFilter.submit(luxLevel); Environment oldEnvironment = currentEnvironment; if (luxLevel < THRESHOLD_NIGHT_LUX){ currentEnvironment = Environment.NIGHT; } else if (luxLevel > THRESHOLD_DAY_LUX){ currentEnvironment = Environment.DAY; } if (hasChanged(oldEnvironment, currentEnvironment)){ callListener(currentEnvironment); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) {} private boolean hasChanged(Environment oldEnvironment, Environment newEnvironment) { return oldEnvironment != newEnvironment; } private void callListener(Environment environment) { if (environmentChangedListener == null || environment == null){ return; } switch (environment) { case DAY: environmentChangedListener.onDayDetected(); break; case NIGHT: environmentChangedListener.onNightDetected(); break; } } } public class LowPassFilter { private float filteredValue; private final float smoothing; private boolean firstTime = true; public LowPassFilter(float smoothing) { this.smoothing = smoothing; } public float submit(float newValue){ if (firstTime){ filteredValue = newValue; firstTime = false; return filteredValue; } filteredValue += (newValue - filteredValue) / smoothing; return filteredValue; } } 


By the way, Android developers have kindly added several constants to the SensorManager class, related to varying degrees of illumination, for example, SensorManager.LIGHT_CLOUDY or SensorManager.LIGHT_FULLMOON .

Well, that's done, the implementation is quite simple. It's great that under the soulless code there is a connection with physics. Using the sensors with which the device is equipped, we can make the application more user-friendly and to some extent interactive. Now you can continue to read without hesitation, regardless of the occurrence of the day or night, arrival in the tunnel or access to the beach.
Moreover, summer is on the way - all run to the beach to read.

Source: https://habr.com/ru/post/255961/


All Articles