AltBeacon Library in Java: Complete Guide for Beacon Detection

Introduction to AltBeacon

AltBeacon is an open-source Android library that provides a standardized beacon format and robust detection capabilities for Bluetooth Low Energy (BLE) beacons. It's the successor to the original Android Beacon Library and offers improved performance, battery efficiency, and reliability.

Key Features

  • Open Standard: Implements the open AltBeacon specification
  • Background Scanning: Efficient scanning even when app is in background
  • Battery Efficient: Optimized scanning cycles to preserve battery
  • Multiple Beacon Formats: Supports iBeacon, Eddystone, and custom formats
  • Region Monitoring: Define geographic regions based on beacon presence
  • Ranging: Measure distance to nearby beacons

Implementation Guide

Dependencies

Add to your app/build.gradle:

dependencies {
implementation 'org.altbeacon:android-beacon-library:2.19.5'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core:1.12.0'
}

AndroidManifest.xml Permissions

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" 
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" 
android:usesPermissionFlags="neverForLocation" 
tools:targetApi="31" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- For Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

Base Application Class

import android.app.Application;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.Region;
public class BeaconApplication extends Application implements MonitorNotifier {
private static final String TAG = "BeaconApplication";
private BeaconManager beaconManager;
@Override
public void onCreate() {
super.onCreate();
setupBeaconManager();
}
private void setupBeaconManager() {
beaconManager = BeaconManager.getInstanceForApplication(this);
// Configure beacon parsers for different formats
beaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
// Add iBeacon parser
beaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
// Add Eddystone-UID parser
beaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("s:0-1=feaa,m:2-2=00,p:3-3:-41,i:4-13,i:14-19"));
// Set scan periods (customize for your use case)
beaconManager.setForegroundScanPeriod(1100L);
beaconManager.setForegroundBetweenScanPeriod(0L);
beaconManager.setBackgroundScanPeriod(1100L);
beaconManager.setBackgroundBetweenScanPeriod(30000L);
// Bind the beacon service
beaconManager.bind(this);
}
@Override
public void didEnterRegion(Region region) {
// Beacon detected in region
android.util.Log.d(TAG, "Entered region: " + region.getUniqueId());
}
@Override
public void didExitRegion(Region region) {
// Exited beacon region
android.util.Log.d(TAG, "Exited region: " + region.getUniqueId());
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
// Region state changed
String stateString = (state == MonitorNotifier.INSIDE) ? "INSIDE" : "OUTSIDE";
android.util.Log.d(TAG, "Region state: " + stateString + " for " + region.getUniqueId());
}
}

Main Activity with Beacon Detection

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class MainActivity extends AppCompatActivity implements BeaconConsumer, RangeNotifier {
private static final String TAG = "MainActivity";
private static final int PERMISSION_REQUEST_CODE = 1001;
private static final String BEACON_REGION_ID = "my-beacon-region";
private BeaconManager beaconManager;
private ListView beaconListView;
private ArrayAdapter<String> beaconAdapter;
private List<String> beaconList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializeUI();
checkPermissions();
}
private void initializeUI() {
beaconListView = findViewById(R.id.beaconListView);
beaconList = new ArrayList<>();
beaconAdapter = new ArrayAdapter<>(this, 
android.R.layout.simple_list_item_1, beaconList);
beaconListView.setAdapter(beaconAdapter);
}
private void checkPermissions() {
List<String> requiredPermissions = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// Android 12+ requires BLUETOOTH_SCAN, BLUETOOTH_CONNECT
if (ContextCompat.checkSelfPermission(this, 
Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.BLUETOOTH_SCAN);
}
if (ContextCompat.checkSelfPermission(this, 
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.BLUETOOTH_CONNECT);
}
} else {
// Older versions need ACCESS_FINE_LOCATION
if (ContextCompat.checkSelfPermission(this, 
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
requiredPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
}
if (!requiredPermissions.isEmpty()) {
ActivityCompat.requestPermissions(this,
requiredPermissions.toArray(new String[0]), PERMISSION_REQUEST_CODE);
} else {
initializeBeaconManager();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
initializeBeaconManager();
} else {
Toast.makeText(this, "Permissions required for beacon detection", 
Toast.LENGTH_LONG).show();
}
}
}
private void initializeBeaconManager() {
beaconManager = BeaconManager.getInstanceForApplication(this);
// Configure for optimal performance
beaconManager.setForegroundScanPeriod(1100L);
beaconManager.setForegroundBetweenScanPeriod(0L);
// Connect to beacon service
beaconManager.bind(this);
}
@Override
public void onBeaconServiceConnect() {
// Set up region monitoring
Region region = new Region(BEACON_REGION_ID, null, null, null);
try {
// Start ranging beacons
beaconManager.startRangingBeacons(region);
beaconManager.addRangeNotifier(this);
// Start monitoring for region entry/exit
beaconManager.startMonitoring(region);
} catch (Exception e) {
android.util.Log.e(TAG, "Failed to start beacon monitoring", e);
}
}
@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
runOnUiThread(() -> updateBeaconList(beacons));
}
private void updateBeaconList(Collection<Beacon> beacons) {
beaconList.clear();
for (Beacon beacon : beacons) {
String beaconInfo = String.format(
"UUID: %s\nMajor: %d Minor: %d\nDistance: %.2fm RSSI: %d\nTxPower: %d",
beacon.getId1().toString(),
beacon.getId2().toInt(),
beacon.getId3().toInt(),
beacon.getDistance(),
beacon.getRssi(),
beacon.getTxPower()
);
beaconList.add(beaconInfo);
}
beaconAdapter.notifyDataSetChanged();
// Log detection for debugging
if (!beacons.isEmpty()) {
android.util.Log.d(TAG, "Detected " + beacons.size() + " beacons");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (beaconManager != null) {
beaconManager.unbind(this);
}
}
}

Advanced Beacon Monitoring Service

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public class BeaconMonitoringService extends Service implements BeaconConsumer, 
MonitorNotifier, RangeNotifier {
private static final String TAG = "BeaconMonitoringService";
private static final String CHANNEL_ID = "beacon_monitoring_channel";
private static final int NOTIFICATION_ID = 1;
private BeaconManager beaconManager;
private final IBinder binder = new LocalBinder();
private Set<BeaconDetectionListener> listeners = new HashSet<>();
public interface BeaconDetectionListener {
void onBeaconDetected(Beacon beacon);
void onBeaconsInRange(Collection<Beacon> beacons);
void onRegionEntered(Region region);
void onRegionExited(Region region);
}
public class LocalBinder extends Binder {
BeaconMonitoringService getService() {
return BeaconMonitoringService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
startForeground(NOTIFICATION_ID, createNotification());
initializeBeaconManager();
}
private void initializeBeaconManager() {
beaconManager = BeaconManager.getInstanceForApplication(this);
// Add beacon parsers
beaconManager.getBeaconParsers().add(new BeaconParser()
.setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
// Background configuration
beaconManager.setBackgroundScanPeriod(1100L);
beaconManager.setBackgroundBetweenScanPeriod(30000L);
beaconManager.setEnableScheduledScanJobs(false);
beaconManager.setBackgroundMode(true);
beaconManager.bind(this);
}
@Override
public void onBeaconServiceConnect() {
beaconManager.addMonitorNotifier(this);
beaconManager.addRangeNotifier(this);
// Monitor all beacons
Region monitoringRegion = new Region("background-region", null, null, null);
try {
beaconManager.startMonitoring(monitoringRegion);
beaconManager.startRangingBeacons(monitoringRegion);
} catch (Exception e) {
android.util.Log.e(TAG, "Cannot start monitoring", e);
}
}
@Override
public void didEnterRegion(Region region) {
android.util.Log.d(TAG, "Entered region: " + region.getUniqueId());
notifyRegionEntered(region);
// Show notification
showRegionNotification("Entered Beacon Region", 
"You have entered a beacon monitoring area");
}
@Override
public void didExitRegion(Region region) {
android.util.Log.d(TAG, "Exited region: " + region.getUniqueId());
notifyRegionExited(region);
showRegionNotification("Exited Beacon Region", 
"You have left a beacon monitoring area");
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
String stateStr = (state == INSIDE) ? "INSIDE" : "OUTSIDE";
android.util.Log.d(TAG, "Region state: " + stateStr + " for " + region.getUniqueId());
}
@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
for (Beacon beacon : beacons) {
android.util.Log.d(TAG, "Ranged beacon: " + beacon.toString());
notifyBeaconDetected(beacon);
}
notifyBeaconsInRange(beacons);
}
// Listener management
public void addBeaconListener(BeaconDetectionListener listener) {
listeners.add(listener);
}
public void removeBeaconListener(BeaconDetectionListener listener) {
listeners.remove(listener);
}
private void notifyBeaconDetected(Beacon beacon) {
for (BeaconDetectionListener listener : listeners) {
listener.onBeaconDetected(beacon);
}
}
private void notifyBeaconsInRange(Collection<Beacon> beacons) {
for (BeaconDetectionListener listener : listeners) {
listener.onBeaconsInRange(beacons);
}
}
private void notifyRegionEntered(Region region) {
for (BeaconDetectionListener listener : listeners) {
listener.onRegionEntered(region);
}
}
private void notifyRegionExited(Region region) {
for (BeaconDetectionListener listener : listeners) {
listener.onRegionExited(region);
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
"Beacon Monitoring",
NotificationManager.IMPORTANCE_LOW
);
channel.setDescription("Background beacon monitoring service");
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
}
private Notification createNotification() {
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(this, CHANNEL_ID);
} else {
builder = new Notification.Builder(this);
}
builder.setContentTitle("Beacon Monitor")
.setContentText("Monitoring for nearby beacons")
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setPriority(Notification.PRIORITY_LOW);
return builder.build();
}
private void showRegionNotification(String title, String content) {
Notification.Builder builder;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder = new Notification.Builder(this, CHANNEL_ID);
} else {
builder = new Notification.Builder(this);
}
Notification notification = builder
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.build();
NotificationManager manager = getSystemService(NotificationManager.class);
manager.notify(NOTIFICATION_ID + 1, notification);
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
@Override
public void onDestroy() {
super.onDestroy();
if (beaconManager != null) {
beaconManager.unbind(this);
}
}
}

Beacon Proximity Manager

import org.altbeacon.beacon.Beacon;
import java.util.HashMap;
import java.util.Map;
public class BeaconProximityManager {
public enum Proximity {
IMMEDIATE, NEAR, FAR, UNKNOWN
}
private static final double IMMEDIATE_THRESHOLD = 0.5; // meters
private static final double NEAR_THRESHOLD = 3.0; // meters
private Map<String, Beacon> trackedBeacons = new HashMap<>();
private Map<String, Long> lastSeenTimestamps = new HashMap<>();
private static final long TIMEOUT_MS = 30000; // 30 seconds
public Proximity calculateProximity(Beacon beacon) {
double distance = beacon.getDistance();
if (distance < 0) {
return Proximity.UNKNOWN;
} else if (distance <= IMMEDIATE_THRESHOLD) {
return Proximity.IMMEDIATE;
} else if (distance <= NEAR_THRESHOLD) {
return Proximity.NEAR;
} else {
return Proximity.FAR;
}
}
public void updateBeacon(Beacon beacon) {
String beaconId = getBeaconId(beacon);
trackedBeacons.put(beaconId, beacon);
lastSeenTimestamps.put(beaconId, System.currentTimeMillis());
}
public void cleanupOldBeacons() {
long currentTime = System.currentTimeMillis();
lastSeenTimestamps.entrySet().removeIf(entry -> 
(currentTime - entry.getValue()) > TIMEOUT_MS);
trackedBeacons.keySet().removeIf(beaconId -> 
!lastSeenTimestamps.containsKey(beaconId));
}
public Map<String, Beacon> getTrackedBeacons() {
return new HashMap<>(trackedBeacons);
}
private String getBeaconId(Beacon beacon) {
return String.format("%s-%s-%s", 
beacon.getId1(), beacon.getId2(), beacon.getId3());
}
public double calculateAverageDistance(Beacon beacon, int samples) {
// Simple moving average for distance smoothing
// In production, implement proper filtering
return beacon.getDistance();
}
}

Layout Files

activity_main.xml

<?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"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Detected Beacons"
android:textSize="18sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginBottom="16dp" />
<ListView
android:id="@+id/beaconListView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/startMonitoringButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Monitoring"
android:layout_marginTop="8dp" />
<Button
android:id="@+id/stopMonitoringButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Stop Monitoring"
android:layout_marginTop="8dp" />
</LinearLayout>

Best Practices

1. Battery Optimization

// Use appropriate scan intervals
beaconManager.setForegroundScanPeriod(1100L);  // Scan for 1.1 seconds
beaconManager.setForegroundBetweenScanPeriod(5000L);  // Wait 5 seconds between scans
// For background, use longer intervals
beaconManager.setBackgroundScanPeriod(1100L);
beaconManager.setBackgroundBetweenScanPeriod(30000L);  // Wait 30 seconds

2. Permission Handling for Different Android Versions

private boolean hasRequiredPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return hasPermission(Manifest.permission.BLUETOOTH_SCAN) &&
hasPermission(Manifest.permission.BLUETOOTH_CONNECT);
} else {
return hasPermission(Manifest.permission.ACCESS_FINE_LOCATION);
}
}

3. Error Handling and Recovery

private void handleBeaconServiceDisconnect() {
try {
beaconManager.unbind(this);
// Rebind after short delay
new android.os.Handler().postDelayed(() -> 
beaconManager.bind(this), 1000);
} catch (Exception e) {
android.util.Log.e(TAG, "Error reconnecting to beacon service", e);
}
}

Testing and Debugging

Enable Debug Logging

// In your Application class
import org.altbeacon.beacon.logging.LogManager;
import org.altbeacon.beacon.logging.Loggers;
public void enableBeaconDebugLogging() {
LogManager.setLogger(Loggers.verboseLogger());
LogManager.setVerboseLoggingEnabled(true);
}

Testing with Mock Beacons

// Create mock beacons for testing
Beacon mockBeacon = new Beacon.Builder()
.setId1("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6")
.setId2("1")
.setId3("2")
.setRssi(-55)
.setTxPower(-59)
.build();

Conclusion

The AltBeacon library provides a robust foundation for beacon detection in Android applications. This implementation covers:

  • Basic beacon detection with proper permission handling
  • Background monitoring for persistent beacon tracking
  • Proximity calculations for distance estimation
  • Best practices for battery efficiency and performance
  • Error handling and recovery mechanisms

Remember to test thoroughly on different Android versions and device models, as Bluetooth behavior can vary significantly across hardware. Always prioritize user privacy and battery life in your implementation.

Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)

https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.

https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.

https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.

https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.

https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)

https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.

https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.

https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.

https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper