Skip to content

Commit 5fef9db

Browse files
committed
Add comprehensive flashlight state persistence functionality to maintain flashlight on/off state across app
sessions and activity lifecycle events. Key Changes: • Add flashlight preference constants and helper methods in Constants.java and PreferencesHelper.java • Implement automatic state saving when flashlight is toggled via toggleFlashlight() method • Add loadFlashlightSettings() method with detailed logging for state restoration debugging • Fix critical timing issue by moving flashlight restoration from onResume() to bindPreviewUseCase() • Ensure flashlight state loads after camera is properly bound and flash unit is available • Add comprehensive debug logging throughout save/restore operations for troubleshooting Technical Details: • Flashlight state now persists in SharedPreferences using SHARED_PREFERENCES_FLASHLIGHT_ENABLED key • State restoration occurs after camera.bindToLifecycle() completes to ensure flash unit availability • Enhanced error handling and logging for camera torch operations and preference access • Maintains consistent behavior with existing capture zone persistence patterns Result: Flashlight toggle state now properly persists across app restarts, activity pauses, and navigation events, providing seamless user experience with maintained lighting preferences.
1 parent df8233e commit 5fef9db

File tree

7 files changed

+263
-2
lines changed

7 files changed

+263
-2
lines changed

AI_MutliBarcodes_Capture/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ android {
1414
applicationId = "com.zebra.ai_multibarcodes_capture.dev"
1515
minSdk = 34
1616
targetSdk = 35
17-
versionCode = 17
18-
versionName = "1.17"
17+
versionCode = 18
18+
versionName = "1.18"
1919

2020
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2121
}

AI_MutliBarcodes_Capture/src/main/java/com/zebra/ai_multibarcodes_capture/helpers/Constants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,9 @@ public class Constants {
190190

191191
public static final String SHARED_PREFERENCES_CAPTURE_ZONE_HEIGHT = "CAPTURE_ZONE_HEIGHT";
192192
public static final int SHARED_PREFERENCES_CAPTURE_ZONE_HEIGHT_DEFAULT = -1; // -1 means not set
193+
194+
// Flashlight preferences
195+
public static final String SHARED_PREFERENCES_FLASHLIGHT_ENABLED = "FLASHLIGHT_ENABLED";
196+
public static final boolean SHARED_PREFERENCES_FLASHLIGHT_ENABLED_DEFAULT = false;
193197

194198
}

AI_MutliBarcodes_Capture/src/main/java/com/zebra/ai_multibarcodes_capture/helpers/PreferencesHelper.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.content.SharedPreferences;
55
import android.widget.Toast;
66

7+
import com.zebra.ai_multibarcodes_capture.helpers.LogUtils;
8+
79
public class PreferencesHelper {
810

911
public static void saveLastSessionFile(Context context, String filePath)
@@ -100,6 +102,23 @@ public static int getCaptureZoneHeight(Context context) {
100102
SharedPreferences sharedPreferences = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
101103
return sharedPreferences.getInt(Constants.SHARED_PREFERENCES_CAPTURE_ZONE_HEIGHT, Constants.SHARED_PREFERENCES_CAPTURE_ZONE_HEIGHT_DEFAULT);
102104
}
105+
106+
// Flashlight preferences methods
107+
108+
public static void saveFlashlightEnabled(Context context, boolean enabled) {
109+
SharedPreferences sharedPreferences = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
110+
SharedPreferences.Editor editor = sharedPreferences.edit();
111+
editor.putBoolean(Constants.SHARED_PREFERENCES_FLASHLIGHT_ENABLED, enabled);
112+
boolean success = editor.commit();
113+
LogUtils.d("PreferencesHelper_FLASHLIGHT", "Saving flashlight state: " + enabled + ", success: " + success);
114+
}
115+
116+
public static boolean isFlashlightEnabled(Context context) {
117+
SharedPreferences sharedPreferences = context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
118+
boolean enabled = sharedPreferences.getBoolean(Constants.SHARED_PREFERENCES_FLASHLIGHT_ENABLED, Constants.SHARED_PREFERENCES_FLASHLIGHT_ENABLED_DEFAULT);
119+
LogUtils.d("PreferencesHelper_FLASHLIGHT", "Loading flashlight state: " + enabled + " (default: " + Constants.SHARED_PREFERENCES_FLASHLIGHT_ENABLED_DEFAULT + ")");
120+
return enabled;
121+
}
103122

104123
}
105124

AI_MutliBarcodes_Capture/src/main/java/com/zebra/ai_multibarcodes_capture/java/CameraXLivePreviewActivity.java

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ public void run() {
132132
private Button captureButton;
133133
private Button closeButton;
134134
private ImageView captureZoneToggleIcon;
135+
private ImageView flashlightToggleIcon;
135136
private CaptureZoneOverlay captureZoneOverlay;
137+
private boolean isFlashlightOn = false;
136138

137139
private BarcodeTracker barcodeTracker;
138140
private EntityBarcodeTracker entityBarcodeTracker;
@@ -232,6 +234,14 @@ public void onClick(View view) {
232234
}
233235
});
234236

237+
flashlightToggleIcon = findViewById(R.id.flashlight_toggle_icon);
238+
flashlightToggleIcon.setOnClickListener(new View.OnClickListener() {
239+
@Override
240+
public void onClick(View view) {
241+
toggleFlashlight();
242+
}
243+
});
244+
235245
initEntityView();
236246
initCaptureZone();
237247
}
@@ -360,6 +370,70 @@ private void loadCaptureZoneSettings() {
360370

361371
LogUtils.d(TAG, "Capture zone settings loaded");
362372
}
373+
374+
private void loadFlashlightSettings() {
375+
LogUtils.d(TAG, "=== loadFlashlightSettings() START ===");
376+
377+
// Load flashlight enabled state
378+
boolean wasFlashlightEnabled = PreferencesHelper.isFlashlightEnabled(this);
379+
380+
LogUtils.d(TAG, "Loaded flashlight state from preferences: " + wasFlashlightEnabled);
381+
LogUtils.d(TAG, "Current camera state: " + (camera != null ? "available" : "null"));
382+
LogUtils.d(TAG, "Camera has flash unit: " + (camera != null && camera.getCameraInfo().hasFlashUnit()));
383+
LogUtils.d(TAG, "Current isFlashlightOn field: " + isFlashlightOn);
384+
LogUtils.d(TAG, "flashlightToggleIcon: " + (flashlightToggleIcon != null ? "available" : "null"));
385+
386+
if (wasFlashlightEnabled && camera != null && camera.getCameraInfo().hasFlashUnit()) {
387+
LogUtils.d(TAG, "Conditions met - restoring flashlight to ON state");
388+
isFlashlightOn = true;
389+
390+
try {
391+
camera.getCameraControl().enableTorch(true);
392+
LogUtils.d(TAG, "Camera torch enabled successfully");
393+
} catch (Exception e) {
394+
LogUtils.e(TAG, "Failed to enable camera torch", e);
395+
}
396+
397+
// Update icon to reflect restored state
398+
if (flashlightToggleIcon != null) {
399+
flashlightToggleIcon.setImageResource(R.drawable.flashlight_on_icon);
400+
LogUtils.d(TAG, "Updated icon to flashlight_on_icon");
401+
} else {
402+
LogUtils.w(TAG, "Cannot update icon - flashlightToggleIcon is null");
403+
}
404+
405+
LogUtils.d(TAG, "Flashlight restored to ON state");
406+
} else {
407+
LogUtils.d(TAG, "Conditions not met - setting flashlight to OFF state");
408+
LogUtils.d(TAG, "Reason: wasEnabled=" + wasFlashlightEnabled +
409+
", camera=" + (camera != null) +
410+
", hasFlash=" + (camera != null && camera.getCameraInfo().hasFlashUnit()));
411+
412+
isFlashlightOn = false;
413+
414+
if (camera != null && camera.getCameraInfo().hasFlashUnit()) {
415+
try {
416+
camera.getCameraControl().enableTorch(false);
417+
LogUtils.d(TAG, "Camera torch disabled successfully");
418+
} catch (Exception e) {
419+
LogUtils.e(TAG, "Failed to disable camera torch", e);
420+
}
421+
}
422+
423+
// Update icon to reflect off state
424+
if (flashlightToggleIcon != null) {
425+
flashlightToggleIcon.setImageResource(R.drawable.flashlight_off_icon);
426+
LogUtils.d(TAG, "Updated icon to flashlight_off_icon");
427+
} else {
428+
LogUtils.w(TAG, "Cannot update icon - flashlightToggleIcon is null");
429+
}
430+
431+
LogUtils.d(TAG, "Flashlight set to OFF state");
432+
}
433+
434+
LogUtils.d(TAG, "Final isFlashlightOn field value: " + isFlashlightOn);
435+
LogUtils.d(TAG, "=== loadFlashlightSettings() END ===");
436+
}
363437

364438
private boolean isBarcodeInCaptureZone(Rect overlayRect) {
365439
// If capture zone is not enabled or overlay is not available, allow all barcodes
@@ -410,6 +484,56 @@ private void toggleCaptureZone() {
410484
}
411485
}
412486

487+
private void toggleFlashlight() {
488+
LogUtils.d(TAG, "=== toggleFlashlight() START ===");
489+
LogUtils.d(TAG, "Current isFlashlightOn: " + isFlashlightOn);
490+
LogUtils.d(TAG, "Camera available: " + (camera != null));
491+
LogUtils.d(TAG, "Has flash unit: " + (camera != null && camera.getCameraInfo().hasFlashUnit()));
492+
493+
if (camera != null && camera.getCameraInfo().hasFlashUnit()) {
494+
boolean oldState = isFlashlightOn;
495+
isFlashlightOn = !isFlashlightOn;
496+
497+
LogUtils.d(TAG, "Toggling flashlight from " + oldState + " to " + isFlashlightOn);
498+
499+
try {
500+
camera.getCameraControl().enableTorch(isFlashlightOn);
501+
LogUtils.d(TAG, "Camera torch set to: " + isFlashlightOn);
502+
} catch (Exception e) {
503+
LogUtils.e(TAG, "Failed to set camera torch to " + isFlashlightOn, e);
504+
}
505+
506+
// Update icon to reflect flashlight state
507+
if (flashlightToggleIcon != null) {
508+
if (isFlashlightOn) {
509+
flashlightToggleIcon.setImageResource(R.drawable.flashlight_on_icon);
510+
LogUtils.d(TAG, "Updated icon to flashlight_on_icon");
511+
} else {
512+
flashlightToggleIcon.setImageResource(R.drawable.flashlight_off_icon);
513+
LogUtils.d(TAG, "Updated icon to flashlight_off_icon");
514+
}
515+
} else {
516+
LogUtils.w(TAG, "Cannot update icon - flashlightToggleIcon is null");
517+
}
518+
519+
// Save flashlight state to preferences
520+
LogUtils.d(TAG, "About to save flashlight state: " + isFlashlightOn);
521+
PreferencesHelper.saveFlashlightEnabled(this, isFlashlightOn);
522+
LogUtils.d(TAG, "Flashlight state saved to preferences");
523+
524+
LogUtils.d(TAG, "Flashlight toggled to: " + (isFlashlightOn ? "ON" : "OFF"));
525+
} else {
526+
LogUtils.w(TAG, "Camera not available or does not have flashlight");
527+
if (flashlightToggleIcon != null) {
528+
// Disable the icon if no flashlight is available
529+
flashlightToggleIcon.setAlpha(0.3f);
530+
LogUtils.d(TAG, "Set flashlight icon alpha to 0.3 (disabled)");
531+
}
532+
}
533+
534+
LogUtils.d(TAG, "=== toggleFlashlight() END ===");
535+
}
536+
413537
private void bindAllCameraUseCases() {
414538
if (cameraProvider != null) {
415539
// As required by CameraX API, unbinds all use cases before trying to re-bind any of them.
@@ -662,6 +786,10 @@ private void bindPreviewUseCase() {
662786
previewUseCase.setSurfaceProvider(binding.previewView.getSurfaceProvider());
663787

664788
camera = cameraProvider.bindToLifecycle(/* lifecycleOwner= */ this, cameraSelector, previewUseCase, analysisUseCase);
789+
790+
// Load flashlight settings after camera is bound and available
791+
LogUtils.d(TAG, "Camera bound successfully, loading flashlight settings");
792+
loadFlashlightSettings();
665793
}
666794

667795
public void onBackPressed() {
@@ -677,6 +805,8 @@ public void onResume() {
677805
LogUtils.d(TAG, "onResume - CaptureZoneOverlay dimensions: " + captureZoneOverlay.getWidth() + "x" + captureZoneOverlay.getHeight());
678806
loadCaptureZoneSettings();
679807
}
808+
809+
// Flashlight settings are now loaded after camera is bound in bindPreviewUseCase()
680810

681811
int currentRotation = getWindowManager().getDefaultDisplay().getRotation();
682812
if (currentRotation != initialRotation) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="24dp"
4+
android:height="24dp"
5+
android:viewportWidth="24"
6+
android:viewportHeight="24">
7+
8+
<!-- Flashlight body -->
9+
<path
10+
android:pathData="M9,2 L15,2 L15,6 L9,6 Z"
11+
android:fillColor="#FFFFFF"
12+
android:strokeColor="#000000"
13+
android:strokeWidth="1" />
14+
15+
<!-- Flashlight neck -->
16+
<path
17+
android:pathData="M8,6 L16,6 L15,8 L9,8 Z"
18+
android:fillColor="#FFFFFF"
19+
android:strokeColor="#000000"
20+
android:strokeWidth="1" />
21+
22+
<!-- Flashlight main body -->
23+
<path
24+
android:pathData="M9,8 L15,8 L15,20 L9,20 Z"
25+
android:fillColor="#FFFFFF"
26+
android:strokeColor="#000000"
27+
android:strokeWidth="1" />
28+
29+
<!-- Power button -->
30+
<path
31+
android:pathData="M10.5,10 L13.5,10 L13.5,11.5 L10.5,11.5 Z"
32+
android:fillColor="#666666" />
33+
34+
<!-- Light beam (off - no beam) -->
35+
36+
</vector>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:width="24dp"
4+
android:height="24dp"
5+
android:viewportWidth="24"
6+
android:viewportHeight="24">
7+
8+
<!-- Flashlight body -->
9+
<path
10+
android:pathData="M9,2 L15,2 L15,6 L9,6 Z"
11+
android:fillColor="#FFFFFF"
12+
android:strokeColor="#000000"
13+
android:strokeWidth="1" />
14+
15+
<!-- Flashlight neck -->
16+
<path
17+
android:pathData="M8,6 L16,6 L15,8 L9,8 Z"
18+
android:fillColor="#FFFFFF"
19+
android:strokeColor="#000000"
20+
android:strokeWidth="1" />
21+
22+
<!-- Flashlight main body -->
23+
<path
24+
android:pathData="M9,8 L15,8 L15,20 L9,20 Z"
25+
android:fillColor="#FFFFFF"
26+
android:strokeColor="#000000"
27+
android:strokeWidth="1" />
28+
29+
<!-- Power button (active/pressed) -->
30+
<path
31+
android:pathData="M10.5,10 L13.5,10 L13.5,11.5 L10.5,11.5 Z"
32+
android:fillColor="#FFD700" />
33+
34+
<!-- Light beam (on - visible beam) -->
35+
<path
36+
android:pathData="M10,2 L14,2 L16,0 L8,0 Z"
37+
android:fillColor="#FFFF80"
38+
android:alpha="0.8" />
39+
40+
<!-- Light rays -->
41+
<path
42+
android:pathData="M5,1 L7,1"
43+
android:strokeColor="#FFFF80"
44+
android:strokeWidth="2" />
45+
<path
46+
android:pathData="M17,1 L19,1"
47+
android:strokeColor="#FFFF80"
48+
android:strokeWidth="2" />
49+
<path
50+
android:pathData="M3,3 L5,3"
51+
android:strokeColor="#FFFF80"
52+
android:strokeWidth="2" />
53+
<path
54+
android:pathData="M19,3 L21,3"
55+
android:strokeColor="#FFFF80"
56+
android:strokeWidth="2" />
57+
58+
</vector>

AI_MutliBarcodes_Capture/src/main/res/layout/activity_camera_xlive_preview.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@
4343
app:layout_constraintTop_toTopOf="@id/preview_view"
4444
app:layout_constraintBottom_toBottomOf="@id/preview_view" />
4545

46+
<ImageView
47+
android:id="@+id/flashlight_toggle_icon"
48+
android:layout_width="56dp"
49+
android:layout_height="56dp"
50+
android:layout_margin="16dp"
51+
android:background="@drawable/capture_zone_icon_background"
52+
android:src="@drawable/flashlight_off_icon"
53+
android:contentDescription="Toggle flashlight"
54+
android:scaleType="center"
55+
android:clickable="true"
56+
android:focusable="true"
57+
app:layout_constraintTop_toTopOf="parent"
58+
app:layout_constraintStart_toStartOf="parent" />
59+
4660
<ImageView
4761
android:id="@+id/capture_zone_toggle_icon"
4862
android:layout_width="56dp"

0 commit comments

Comments
 (0)