@@ -339,6 +339,43 @@ static final class SystemEventsBroadcastReceiver extends BroadcastReceiver {
339339 private final @ NotNull Debouncer batteryChangedDebouncer =
340340 new Debouncer (AndroidCurrentDateProvider .getInstance (), DEBOUNCE_WAIT_TIME_MS , 0 );
341341
342+ // Track previous battery state to avoid duplicate breadcrumbs when values haven't changed
343+ private @ Nullable BatteryState previousBatteryState ;
344+
345+ static final class BatteryState {
346+ private final @ Nullable Float level ;
347+ private final @ Nullable Boolean charging ;
348+
349+ BatteryState (final @ Nullable Float level , final @ Nullable Boolean charging ) {
350+ this .level = level ;
351+ this .charging = charging ;
352+ }
353+
354+ @ Override
355+ public boolean equals (final @ Nullable Object other ) {
356+ if (!(other instanceof BatteryState )) return false ;
357+ BatteryState that = (BatteryState ) other ;
358+ return isSimilarLevel (level , that .level ) && Objects .equals (charging , that .charging );
359+ }
360+
361+ @ Override
362+ public int hashCode () {
363+ // Use rounded level for hash consistency
364+ Float roundedLevel = level != null ? Math .round (level * 100f ) / 100f : null ;
365+ return Objects .hash (roundedLevel , charging );
366+ }
367+
368+ private boolean isSimilarLevel (final @ Nullable Float level1 , final @ Nullable Float level2 ) {
369+ if (level1 == null && level2 == null ) return true ;
370+ if (level1 == null || level2 == null ) return false ;
371+
372+ // Round both levels to 2 decimal places and compare
373+ float rounded1 = Math .round (level1 * 100f ) / 100f ;
374+ float rounded2 = Math .round (level2 * 100f ) / 100f ;
375+ return rounded1 == rounded2 ;
376+ }
377+ }
378+
342379 SystemEventsBroadcastReceiver (
343380 final @ NotNull IScopes scopes , final @ NotNull SentryAndroidOptions options ) {
344381 this .scopes = scopes ;
@@ -350,19 +387,34 @@ public void onReceive(final Context context, final @NotNull Intent intent) {
350387 final @ Nullable String action = intent .getAction ();
351388 final boolean isBatteryChanged = ACTION_BATTERY_CHANGED .equals (action );
352389
353- // aligning with iOS which only captures battery status changes every minute at maximum
354- if (isBatteryChanged && batteryChangedDebouncer .checkForDebounce ()) {
355- return ;
390+ @ Nullable BatteryState batteryState = null ;
391+ if (isBatteryChanged ) {
392+ if (batteryChangedDebouncer .checkForDebounce ()) {
393+ // aligning with iOS which only captures battery status changes every minute at maximum
394+ return ;
395+ }
396+
397+ // For battery changes, check if the actual values have changed
398+ final @ Nullable Float currentBatteryLevel = DeviceInfoUtil .getBatteryLevel (intent , options );
399+ final @ Nullable Boolean currentChargingState = DeviceInfoUtil .isCharging (intent , options );
400+ batteryState = new BatteryState (currentBatteryLevel , currentChargingState );
401+
402+ // Only create breadcrumb if battery state has actually changed
403+ if (batteryState .equals (previousBatteryState )) {
404+ return ;
405+ }
406+
407+ previousBatteryState = batteryState ;
356408 }
357409
410+ final BatteryState state = batteryState ;
358411 final long now = System .currentTimeMillis ();
359412 try {
360413 options
361414 .getExecutorService ()
362415 .submit (
363416 () -> {
364- final Breadcrumb breadcrumb =
365- createBreadcrumb (now , intent , action , isBatteryChanged );
417+ final Breadcrumb breadcrumb = createBreadcrumb (now , intent , action , state );
366418 final Hint hint = new Hint ();
367419 hint .set (ANDROID_INTENT , intent );
368420 scopes .addBreadcrumb (breadcrumb , hint );
@@ -411,7 +463,7 @@ String getStringAfterDotFast(final @Nullable String str) {
411463 final long timeMs ,
412464 final @ NotNull Intent intent ,
413465 final @ Nullable String action ,
414- boolean isBatteryChanged ) {
466+ final @ Nullable BatteryState batteryState ) {
415467 final Breadcrumb breadcrumb = new Breadcrumb (timeMs );
416468 breadcrumb .setType ("system" );
417469 breadcrumb .setCategory ("device.event" );
@@ -420,14 +472,12 @@ String getStringAfterDotFast(final @Nullable String str) {
420472 breadcrumb .setData ("action" , shortAction );
421473 }
422474
423- if (isBatteryChanged ) {
424- final Float batteryLevel = DeviceInfoUtil .getBatteryLevel (intent , options );
425- if (batteryLevel != null ) {
426- breadcrumb .setData ("level" , batteryLevel );
475+ if (batteryState != null ) {
476+ if (batteryState .level != null ) {
477+ breadcrumb .setData ("level" , batteryState .level );
427478 }
428- final Boolean isCharging = DeviceInfoUtil .isCharging (intent , options );
429- if (isCharging != null ) {
430- breadcrumb .setData ("charging" , isCharging );
479+ if (batteryState .charging != null ) {
480+ breadcrumb .setData ("charging" , batteryState .charging );
431481 }
432482 } else {
433483 final Bundle extras = intent .getExtras ();
0 commit comments