Sensor-Based Flash On/Off in Android: Proximity Sensor Tutorial (2026)
Last Updated: | Author: Deep Singh
Android phones are packed with sensors — accelerometer, gyroscope, magnetometer, and the one we're using today: the proximity sensor. It's the tiny sensor near the earpiece that detects when you hold the phone to your face during a call.
In this tutorial, you'll build a small app that turns the camera flash on when you bring your hand close to the sensor, and turns it off when you pull it away. It's a fun, hands-on way to learn how SensorManager and SensorEventListener work together.
Important heads-up about the Camera API: This tutorial uses the legacy android.hardware.Camera API, which was deprecated in API 21 and removed in Android 14 (API 34). If you're building for modern Android, you must use CameraX or Camera2 instead. The proximity sensor part of the tutorial is still perfectly valid. I'll show both the classic version and the modern replacement at the end.
How It Works
The proximity sensor reports a single value: the distance (in centimetres) from the sensor to the nearest object.
- When nothing is near, the sensor reports the maximum value (often 5 cm or more on many devices).
- When you cover the sensor with your hand, it reports
0.0.
Our logic is simple:
- Value is
0.0→ user is close → turn the flash on. - Value is anything else → user is far → turn the flash off.
To turn the flash on, we need to access the camera and set FLASH_MODE_TORCH. On older Android versions, this was done with the Camera class. On modern versions, CameraX is the correct approach.
AndroidManifest.xml
Add the camera permission. Note the use of uses-permission-sdk-23, which tells Android to only apply the permission on API 23 and above (where runtime permissions were introduced).
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.secondapp">
<uses-permission-sdk-23
android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Also add the feature declaration for the camera: Since Android 5.0, apps that use the camera should declare it as a feature. Add this line inside the <manifest> tag:
<uses-feature android:name="android.hardware.camera" />
activity_main.xml
The layout is minimal — just a TextView to display the sensor value. In a real app you might hide it or show it only for debugging.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/data"
android:paddingBottom="20dp"
android:textSize="20sp" />
</LinearLayout>
MainActivity.java (Legacy Camera API)
This is the classic approach used in the original 2021 tutorial. It uses SensorManager to register a listener and the deprecated Camera class to control the flash.
package com.example.secondapp;
import androidx.appcompat.app.AppCompatActivity;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private SensorManager sm;
private TextView mydata;
private Camera camera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mydata = findViewById(R.id.data);
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor proximitySensor = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
if (proximitySensor != null) {
sm.registerListener(
sensorListener,
proximitySensor,
SensorManager.SENSOR_DELAY_NORMAL
);
}
}
private final SensorEventListener sensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
float value = event.values[0];
mydata.setText(String.valueOf(value));
if (value == 0.0f) {
mydata.setText("near");
flashOn();
} else {
mydata.setText("so far");
flashOff();
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.d("Sensor", "Accuracy changed: " + accuracy);
}
};
@Override
protected void onStop() {
super.onStop();
if (sm != null) {
sm.unregisterListener(sensorListener);
}
flashOff(); // Ensure the flash is off when the app is backgrounded
}
@SuppressWarnings("deprecation")
private void flashOn() {
if (camera != null) return; // Already on
try {
camera = Camera.open();
Camera.Parameters params = camera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
camera.setParameters(params);
camera.startPreview();
} catch (Exception e) {
Log.e("Flash", "Failed to turn on", e);
}
}
@SuppressWarnings("deprecation")
private void flashOff() {
if (camera != null) {
try {
camera.stopPreview();
camera.release();
} catch (Exception e) {
Log.e("Flash", "Failed to turn off", e);
} finally {
camera = null;
}
}
}
}
What's been fixed from the original:
- Removed commented-out debug code. The original had a large commented block iterating all sensors and several inline Log statements. Removed for clarity.
- Declared the listener as a field. The original created the
SensorEventListeneras an anonymous class insideonCreatebut then referenced it as a field calledseinonStop. That wouldn't compile. It's now a proper field. - Added a null check on `sm` in `onStop`. Defensive.
- Added `flashOff()` in `onStop`. Otherwise, the flash stays on when the user backgrounds the app — very annoying and drains the battery.
- Guarded `flashOn` against being called twice. If the sensor fires multiple times while close, we shouldn't try to open the camera repeatedly.
- Added `@SuppressWarnings("deprecation")`. The Camera API is deprecated, and the warnings are expected — but this keeps the code clean.
- Changed `Log.e("error", ...)` to `Log.d` or `Log.e` appropriately. The original used ERROR level for normal messages.
- Changed `float value[]` to `float value` — no reason to declare an array type explicitly.
- Used `Sensor.TYPE_PROXIMITY` directly. The original used this too, but with unnecessary extra comments.
Modern Alternative: CameraX
The android.hardware.Camera class was removed in Android 14. If you're targeting modern Android, use CameraX. Here's the modern equivalent of the flash control logic.
build.gradle dependency
dependencies {
def camerax_version = "1.4.0"
implementation "androidx.camera:camera-core:$camerax_version"
implementation "androidx.camera:camera-camera2:$camerax_version"
implementation "androidx.camera:camera-lifecycle:$camerax_version"
implementation "androidx.camera:camera-view:$camerax_version"
}
Updated flash methods
import androidx.camera.core.CameraSelector;
import androidx.camera.core.TorchState;
import androidx.camera.lifecycle.ProcessCameraProvider;
import androidx.core.content.ContextCompat;
import com.google.common.util.concurrent.ListenableFuture;
public class MainActivity extends AppCompatActivity {
private ProcessCameraProvider cameraProvider;
private androidx.camera.core.Camera camera;
private void initCamera() {
ListenableFuture<ProcessCameraProvider> future =
ProcessCameraProvider.getInstance(this);
future.addListener(() -> {
try {
cameraProvider = future.get();
camera = cameraProvider.bindToLifecycle(
this,
CameraSelector.DEFAULT_BACK_CAMERA
);
} catch (Exception e) {
Log.e("Camera", "Init failed", e);
}
}, ContextCompat.getMainExecutor(this));
}
private void flashOn() {
if (camera != null && camera.getCameraInfo().hasFlashUnit()) {
camera.getCameraControl().enableTorch(true);
}
}
private void flashOff() {
if (camera != null) {
camera.getCameraControl().enableTorch(false);
}
}
}
The full CameraX setup requires more boilerplate (a PreviewView, a use case, etc.), but the flash control itself is much cleaner. The proximity sensor code above stays exactly the same.
Note: On Android 13+ you also need to request the POST_NOTIFICATIONS permission if you plan to show anything in the notification shade. For a plain activity with a TextView, nothing extra is needed.
Common Pitfalls
- Legacy Camera API is removed in Android 14. If your
compileSdkis 34 or above and you try to useandroid.hardware.Camera, the build fails. Use CameraX. - Forgetting to unregister the sensor listener. If you don't call
unregisterListenerinonStoporonPause, the sensor stays active and drains the battery. Always unregister. - Forgetting to release the camera. Holding a camera reference locks the camera for the whole app. If the user opens a different camera app, it will fail. Always release the camera in
onStop. - Not requesting the runtime permission.
CAMERAis a dangerous permission. On Android 6+ you must request it at runtime withActivityCompat.requestPermissionsor the Activity Result API. - Assuming the proximity sensor exists. Not all devices have one. Always check for
nullbefore registering a listener, or your app crashes. - Assuming the value is exactly 0.0. Some devices report very small non-zero values when close. Use a threshold like
value < 1.0finstead ofvalue == 0.0f. - Opening the camera on the main thread.
Camera.open()can be slow and cause an ANR (Application Not Responding). Open it on a background thread if you need to be strict about performance. - Leaving the flash on in the background. Very common bug. Always turn off the flash in
onStoporonPause. - Using
Log.efor informational messages. It's semantically wrong and pollutes production logs. UseLog.dfor debug output and reserveLog.efor actual errors. - Camera permission on Android 13+. Only
CAMERAis needed for flash control. Don't requestREAD_MEDIA_IMAGESunless you're actually reading photos.
Wrapping Up
You've now built an app that responds to the proximity sensor and controls the camera flash accordingly. The important concepts you learned are:
- Getting a
SensorManagerand a specific sensor from it. - Registering an
SensorEventListenerto receive updates. - Unregistering the listener in the appropriate lifecycle method.
- Using the flash in torch mode via the camera API (and knowing the modern alternative).
From here, you can extend this project to:
- Use other sensors. Replace
TYPE_PROXIMITYwithTYPE_ACCELEROMETERorTYPE_LIGHTto trigger different actions. - Toggle instead of hold. Instead of turning the flash on/off based on the sensor state, use it as a toggle — approach = on, away = off, approach again = on.
- Add a delay. Sometimes the sensor fires many times per second. Debounce the input so the flash doesn't flicker.
- Battery considerations. The camera flash uses a lot of power. Consider limiting how long it can stay on.