Version 0.4.0 Alpha¶
Release Date: August 11, 2024
Status: Alpha Release
Build Number: 0.4.0
Overview¶
Version 0.4.0 represents a transformative release for MINE - Indoor Navigation Engine, introducing real-time user navigation capabilities that bring the complete indoor navigation experience to life. This release bridges the gap between static path finding and dynamic, real-time turn-by-turn navigation with live position tracking and adaptive routing.
Production-Ready Alpha
With robust location tracking, comprehensive permission handling, and optimized performance, v0.4.0 is suitable for production pilots and real-world deployments.
Highlights¶
-
Live Navigation
Real-time turn-by-turn navigation with dynamic position updates
-
Location Tracking
Precise indoor positioning with multiple location providers
-
Smart Instructions
Context-aware directions that adapt to user behavior
-
Permission Manager
Seamless permission handling with user-friendly flows
New Features¶
User Navigation Feature¶
A complete navigation system that guides users from their current location to their destination with real-time updates.
Core Capabilities:
- ๐งญ Real-time Position Updates - Track user movement with sub-meter accuracy
- ๐ Automatic Rerouting - Dynamically adjust route when user deviates
- ๐ฏ Arrival Detection - Automatically detect destination arrival
- ๐ข Progress Notifications - Keep users informed of navigation progress
- ๐ถ Multi-modal Support - Walking, wheelchair, and custom movement modes
- ๐ Offline Navigation - Continue navigation without internet connectivity
- ๐ Navigation Analytics - Track navigation sessions and user behavior
Implementation:
import com.machinestalk.indoornavigationengine.navigation.UserNavigationManager
import com.machinestalk.indoornavigationengine.navigation.NavigationMode
import com.machinestalk.indoornavigationengine.models.Location
class NavigationActivity : AppCompatActivity() {
private lateinit var navigationManager: UserNavigationManager
private lateinit var sceneView: MineSceneView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sceneView = MineSceneView(this)
setContentView(sceneView)
// Initialize navigation manager
setupNavigation()
}
private fun setupNavigation() {
navigationManager = UserNavigationManager(this, sceneView).apply {
// Configure navigation mode
setNavigationMode(NavigationMode.WALKING)
// Set rerouting parameters
setReroutingConfig(ReroutingConfig(
enabled = true,
deviationThreshold = 5f, // meters
autoRerouteDelay = 2000 // milliseconds
))
// Configure arrival detection
setArrivalConfig(ArrivalConfig(
radius = 3f, // meters
dwellTime = 2000 // milliseconds
))
// Listen to navigation events
setNavigationListener(object : NavigationListener {
override fun onNavigationStarted(route: Route) {
showNotification("Navigation started")
}
override fun onPositionUpdated(location: Location) {
updateMapPosition(location)
}
override fun onInstructionChanged(instruction: NavigationInstruction) {
displayInstruction(instruction)
}
override fun onRouteDeviation(deviationDistance: Float) {
showWarning("Off route by ${deviationDistance}m")
}
override fun onRerouting() {
showNotification("Calculating new route...")
}
override fun onArrival() {
showSuccess("You have arrived!")
stopNavigation()
}
override fun onNavigationCancelled() {
clearRoute()
}
override fun onNavigationError(error: NavigationError) {
showError("Navigation error: ${error.message}")
}
})
}
}
fun startNavigation(destinationId: String) {
val destination = sceneView.getPOIById(destinationId)
destination?.let {
navigationManager.startNavigation(
destination = it.location,
options = NavigationOptions(
voiceGuidance = true,
vibrationFeedback = true,
showProgressBar = true
)
)
}
}
fun stopNavigation() {
navigationManager.stopNavigation()
}
override fun onDestroy() {
navigationManager.destroy()
super.onDestroy()
}
}
import com.machinestalk.indoornavigationengine.navigation.UserNavigationManager;
import com.machinestalk.indoornavigationengine.navigation.NavigationMode;
import com.machinestalk.indoornavigationengine.models.Location;
public class NavigationActivity extends AppCompatActivity {
private UserNavigationManager navigationManager;
private MineSceneView sceneView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sceneView = new MineSceneView(this);
setContentView(sceneView);
setupNavigation();
}
private void setupNavigation() {
navigationManager = new UserNavigationManager(this, sceneView);
// Configure navigation mode
navigationManager.setNavigationMode(NavigationMode.WALKING);
// Set rerouting parameters
ReroutingConfig reroutingConfig = new ReroutingConfig.Builder()
.setEnabled(true)
.setDeviationThreshold(5f)
.setAutoRerouteDelay(2000)
.build();
navigationManager.setReroutingConfig(reroutingConfig);
// Listen to navigation events
navigationManager.setNavigationListener(new NavigationListener() {
@Override
public void onNavigationStarted(Route route) {
showNotification("Navigation started");
}
@Override
public void onPositionUpdated(Location location) {
updateMapPosition(location);
}
@Override
public void onInstructionChanged(NavigationInstruction instruction) {
displayInstruction(instruction);
}
@Override
public void onArrival() {
showSuccess("You have arrived!");
stopNavigation();
}
});
}
public void startNavigation(String destinationId) {
POI destination = sceneView.getPOIById(destinationId);
if (destination != null) {
NavigationOptions options = new NavigationOptions.Builder()
.setVoiceGuidance(true)
.setVibrationFeedback(true)
.setShowProgressBar(true)
.build();
navigationManager.startNavigation(destination.getLocation(), options);
}
}
}
Navigation Modes:
| Mode | Speed | Description | Use Case |
|---|---|---|---|
WALKING |
1.4 m/s | Standard walking pace | General navigation |
WHEELCHAIR |
1.0 m/s | Accessible routes only | Accessibility needs |
RUNNING |
2.5 m/s | Faster movement | Fitness/emergency |
CUSTOM |
Variable | User-defined parameters | Special requirements |
Navigation UI Utilities¶
Comprehensive UI components designed specifically for real-time navigation experiences.
Available Components:
Live Navigation Panel¶
import com.machinestalk.indoornavigationengine.ui.navigation.LiveNavigationPanel
val navigationPanel = LiveNavigationPanel(this).apply {
// Configure layout
setPosition(LiveNavigationPanel.Position.TOP)
setStyle(LiveNavigationPanel.Style.COMPACT)
// Configure content
showDistanceToNextTurn = true
showEstimatedArrival = true
showCurrentSpeed = false
// Customize appearance
setBackgroundColor(Color.parseColor("#2196F3"))
setTextColor(Color.WHITE)
setIconTint(Color.WHITE)
// Enable animations
enableSmoothTransitions = true
transitionDuration = 300
// Handle user interactions
setOnPanelClickListener {
showFullDirections()
}
setOnCancelClickListener {
confirmCancelNavigation()
}
}
sceneView.addUIComponent(navigationPanel)
Route Overview Card¶
import com.machinestalk.indoornavigationengine.ui.navigation.RouteOverviewCard
val overviewCard = RouteOverviewCard(this).apply {
// Set route information
setRoute(currentRoute)
// Display key metrics
showTotalDistance = true
showTotalTime = true
showFloorChanges = true
showElevatorCount = true
// Show upcoming steps preview
setUpcomingStepsCount(3)
// Enable interactive features
enableStepNavigation = true
enableRoutePreview = true
// Handle interactions
setOnStepClickListener { stepIndex ->
previewNavigationStep(stepIndex)
}
setOnStartNavigationClickListener {
navigationManager.startNavigation(currentRoute)
dismissCard()
}
}
showBottomSheet(overviewCard)
Navigation Compass¶
import com.machinestalk.indoornavigationengine.ui.navigation.NavigationCompass
val compass = NavigationCompass(this).apply {
// Position on screen
setPosition(NavigationCompass.Position.TOP_LEFT)
setMargin(16.dp)
// Configure appearance
setSize(64.dp)
setCompassStyle(NavigationCompass.Style.MODERN)
// Enable features
showNorthIndicator = true
showDestinationDirection = true
enableRotation = true
// Update with navigation data
setDestinationBearing(navigationManager.getDestinationBearing())
setCurrentHeading(locationManager.getCurrentHeading())
}
sceneView.addUIComponent(compass)
Speed and ETA Display¶
import com.machinestalk.indoornavigationengine.ui.navigation.SpeedETADisplay
val speedDisplay = SpeedETADisplay(this).apply {
// Position
setPosition(SpeedETADisplay.Position.BOTTOM_LEFT)
// Configure display units
setSpeedUnit(SpeedUnit.METERS_PER_SECOND)
setDistanceUnit(DistanceUnit.METERS)
// Update in real-time
navigationManager.addSpeedListener { speed ->
updateSpeed(speed)
}
navigationManager.addETAListener { eta ->
updateETA(eta)
}
// Show additional info
showDistanceRemaining = true
showTimeRemaining = true
}
sceneView.addUIComponent(speedDisplay)
User Directions and Instructions¶
Context-aware, intelligent navigation instructions that adapt to user behavior and position.
Features:
- ๐ Natural Language Instructions - Easy-to-understand directions
- ๐ฏ Distance-based Triggers - Instructions appear at optimal distances
- ๐ Text-to-Speech Support - Voice guidance integration
- ๐ Multi-language Support - Localized instructions
- ๐จ Rich Visual Cues - Icons and graphics for clarity
- ๐ Progressive Disclosure - Show details as needed
Implementation:
import com.machinestalk.indoornavigationengine.navigation.InstructionManager
import com.machinestalk.indoornavigationengine.navigation.InstructionType
class InstructionController(private val context: Context) {
private val instructionManager = InstructionManager(context)
fun setupInstructions() {
instructionManager.apply {
// Configure instruction generation
setInstructionConfig(InstructionConfig(
// Distance triggers
earlyWarningDistance = 50f, // meters
turnAnnouncementDistance = 20f,
arrivalAnnouncementDistance = 10f,
// Verbosity level
verbosity = InstructionVerbosity.DETAILED,
// Language
locale = Locale.getDefault(),
// Enable features
includeLandmarks = true,
includeFloorInfo = true,
includeDistances = true
))
// Custom instruction templates
setInstructionTemplate(
type = InstructionType.TURN_LEFT,
template = "Turn left {landmark} in {distance}"
)
// Listen for instruction updates
setInstructionListener { instruction ->
handleInstruction(instruction)
}
}
}
private fun handleInstruction(instruction: NavigationInstruction) {
when (instruction.type) {
InstructionType.START -> {
showInstruction("Head ${instruction.direction}")
}
InstructionType.TURN_LEFT -> {
val landmark = instruction.landmark?.let { " at the $it" } ?: ""
showInstruction("Turn left$landmark in ${instruction.distance}m")
}
InstructionType.TURN_RIGHT -> {
val landmark = instruction.landmark?.let { " at the $it" } ?: ""
showInstruction("Turn right$landmark in ${instruction.distance}m")
}
InstructionType.GO_STRAIGHT -> {
showInstruction("Continue straight for ${instruction.distance}m")
}
InstructionType.CHANGE_FLOOR -> {
val method = instruction.floorChangeMethod // elevator, stairs, escalator
showInstruction("Take the $method to ${instruction.targetFloor}")
}
InstructionType.ARRIVE -> {
showInstruction("You have arrived at your destination")
}
}
// Trigger voice guidance if enabled
if (instruction.voiceEnabled) {
speakInstruction(instruction.text)
}
// Trigger haptic feedback if enabled
if (instruction.hapticEnabled) {
vibrate(instruction.hapticPattern)
}
}
private fun speakInstruction(text: String) {
// Text-to-speech implementation
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, null)
}
}
Instruction Types:
| Type | Trigger Distance | Icon | Voice |
|---|---|---|---|
START |
0m | ๐ฏ | "Head [direction]" |
TURN_LEFT |
20m | โฌ ๏ธ | "Turn left in [distance]" |
TURN_RIGHT |
20m | โก๏ธ | "Turn right in [distance]" |
GO_STRAIGHT |
50m | โฌ๏ธ | "Continue straight" |
CHANGE_FLOOR |
30m | ๐ผ | "Take [method] to floor [X]" |
ARRIVE |
10m | ๐ | "You have arrived" |
Customization Example:
// Create custom instruction formatter
class CustomInstructionFormatter : InstructionFormatter {
override fun format(instruction: NavigationInstruction): String {
return when (instruction.type) {
InstructionType.TURN_LEFT -> {
"๐ Make a left turn at the ${instruction.landmark ?: "next intersection"}"
}
InstructionType.CHANGE_FLOOR -> {
"๐ผ Go to ${instruction.targetFloor} using the ${instruction.floorChangeMethod}"
}
else -> instruction.defaultText
}
}
override fun formatWithDistance(instruction: NavigationInstruction): String {
val distanceText = when {
instruction.distance < 10 -> "very soon"
instruction.distance < 25 -> "in ${instruction.distance.toInt()} meters"
else -> "ahead"
}
return "${format(instruction)} $distanceText"
}
}
// Apply custom formatter
instructionManager.setFormatter(CustomInstructionFormatter())
User Location Tracking¶
High-precision indoor positioning system with multiple location providers and sensor fusion.
Location Providers:
- ๐ก WiFi Positioning - WiFi fingerprinting and trilateration
- ๐ต Bluetooth Beacons - iBeacon and Eddystone support
- ๐ฐ๏ธ GPS (when available) - Outdoor-to-indoor transitions
- ๐ฒ Sensor Fusion - Accelerometer, gyroscope, magnetometer
- ๐บ๏ธ PDR (Pedestrian Dead Reckoning) - Step counting and heading
- ๐ฏ Custom Providers - Integrate your own positioning system
Implementation:
import com.machinestalk.indoornavigationengine.location.LocationTracker
import com.machinestalk.indoornavigationengine.location.LocationProvider
class LocationTrackingManager(private val context: Context) {
private val locationTracker = LocationTracker(context)
fun setupLocationTracking() {
locationTracker.apply {
// Configure location providers
addProvider(LocationProvider.WIFI, priority = 10)
addProvider(LocationProvider.BLUETOOTH, priority = 8)
addProvider(LocationProvider.SENSOR_FUSION, priority = 6)
addProvider(LocationProvider.PDR, priority = 4)
// Set accuracy requirements
setMinAccuracy(5f) // meters
setUpdateInterval(1000) // milliseconds
setFastestInterval(500)
// Configure sensor fusion
setSensorFusionConfig(SensorFusionConfig(
enableAccelerometer = true,
enableGyroscope = true,
enableMagnetometer = true,
enableStepDetector = true
))
// Set WiFi scanning parameters
setWiFiConfig(WiFiConfig(
scanInterval = 5000,
minRSSI = -90,
fingerprintDatabase = loadFingerprintDatabase()
))
// Set Bluetooth beacon parameters
setBeaconConfig(BeaconConfig(
scanInterval = 1000,
rangingEnabled = true,
supportedTypes = listOf(BeaconType.IBEACON, BeaconType.EDDYSTONE)
))
// Listen to location updates
setLocationListener(object : LocationListener {
override fun onLocationChanged(location: Location) {
handleLocationUpdate(location)
}
override fun onAccuracyChanged(accuracy: Float) {
updateAccuracyIndicator(accuracy)
}
override fun onProviderChanged(provider: LocationProvider) {
Log.d("Location", "Provider changed to: ${provider.name}")
}
override fun onLocationLost() {
showWarning("Location signal lost")
}
})
}
}
fun startTracking() {
if (hasLocationPermission()) {
locationTracker.startTracking()
} else {
requestLocationPermission()
}
}
fun stopTracking() {
locationTracker.stopTracking()
}
private fun handleLocationUpdate(location: Location) {
// Update user position on map
sceneView.updateUserLocation(
location = location,
accuracy = location.accuracy,
heading = location.bearing
)
// Check if user is on correct floor
val currentFloor = sceneView.getCurrentFloor()
if (location.floor != currentFloor.id) {
sceneView.switchToFloor(location.floor)
}
// Update navigation if active
navigationManager.updateUserLocation(location)
}
}
import com.machinestalk.indoornavigationengine.location.LocationTracker;
import com.machinestalk.indoornavigationengine.location.LocationProvider;
public class LocationTrackingManager {
private LocationTracker locationTracker;
public LocationTrackingManager(Context context) {
this.locationTracker = new LocationTracker(context);
setupLocationTracking();
}
private void setupLocationTracking() {
// Configure providers
locationTracker.addProvider(LocationProvider.WIFI, 10);
locationTracker.addProvider(LocationProvider.BLUETOOTH, 8);
locationTracker.addProvider(LocationProvider.SENSOR_FUSION, 6);
// Set parameters
locationTracker.setMinAccuracy(5f);
locationTracker.setUpdateInterval(1000);
// Listen to updates
locationTracker.setLocationListener(new LocationListener() {
@Override
public void onLocationChanged(Location location) {
handleLocationUpdate(location);
}
@Override
public void onAccuracyChanged(float accuracy) {
updateAccuracyIndicator(accuracy);
}
});
}
public void startTracking() {
if (hasLocationPermission()) {
locationTracker.startTracking();
} else {
requestLocationPermission();
}
}
}
Location Accuracy:
| Provider | Typical Accuracy | Update Rate | Power Usage |
|---|---|---|---|
| WiFi | 3-5m | 1-5s | Medium |
| Bluetooth | 1-3m | 0.5-2s | Low |
| Sensor Fusion | 2-8m | 0.1-1s | High |
| PDR | 5-15m | 0.1-0.5s | Low |
| Combined | 1-3m | 0.5-1s | Medium-High |
Permission Handling¶
Comprehensive permission management system with user-friendly flows and fallback options.
Features:
- โ Runtime Permission Requests - Modern Android permission handling
- ๐ Permission Rationale - Clear explanations for users
- ๐ Graceful Degradation - Fallback options when permissions denied
- โ๏ธ Settings Integration - Direct links to app settings
- ๐ Permission Analytics - Track permission grant rates
Implementation:
import com.machinestalk.indoornavigationengine.permissions.PermissionManager
import com.machinestalk.indoornavigationengine.permissions.PermissionType
class PermissionHandler(private val activity: Activity) {
private val permissionManager = PermissionManager(activity)
fun setupPermissions() {
permissionManager.apply {
// Set permission requirements
requirePermission(
type = PermissionType.LOCATION_FINE,
required = true,
rationale = "We need your location to provide turn-by-turn navigation"
)
requirePermission(
type = PermissionType.BLUETOOTH,
required = false,
rationale = "Bluetooth improves indoor positioning accuracy"
)
requirePermission(
type = PermissionType.WIFI_STATE,
required = false,
rationale = "WiFi helps determine your position indoors"
)
// Configure UI
setPermissionUI(PermissionUIConfig(
showRationale = true,
allowDenyForever = true,
customRationaleDialog = false
))
// Handle permission results
setPermissionCallback(object : PermissionCallback {
override fun onPermissionsGranted(permissions: List<PermissionType>) {
// Start location tracking
if (PermissionType.LOCATION_FINE in permissions) {
startLocationTracking()
}
// Enable Bluetooth scanning
if (PermissionType.BLUETOOTH in permissions) {
enableBluetoothScanning()
}
}
override fun onPermissionsDenied(
permissions: List<PermissionType>,
permanentlyDenied: List<PermissionType>
) {
if (PermissionType.LOCATION_FINE in permanentlyDenied) {
showPermanentlyDeniedDialog()
} else {
showDeniedMessage()
}
}
override fun onPermissionsRevoked(permissions: List<PermissionType>) {
// Handle permission revocation
if (PermissionType.LOCATION_FINE in permissions) {
stopLocationTracking()
}
}
})
}
}
fun requestNavigationPermissions() {
permissionManager.requestPermissions(
permissions = listOf(
PermissionType.LOCATION_FINE,
PermissionType.BLUETOOTH,
PermissionType.WIFI_STATE
),
showRationale = true
)
}
private fun showPermanentlyDeniedDialog() {
AlertDialog.Builder(activity)
.setTitle("Location Permission Required")
.setMessage(
"Navigation requires location permission. " +
"Please enable it in app settings."
)
.setPositiveButton("Open Settings") { _, _ ->
permissionManager.openAppSettings()
}
.setNegativeButton("Cancel", null)
.show()
}
fun hasRequiredPermissions(): Boolean {
return permissionManager.hasPermission(PermissionType.LOCATION_FINE)
}
fun checkAndRequestPermissions(onGranted: () -> Unit) {
if (hasRequiredPermissions()) {
onGranted()
} else {
requestNavigationPermissions()
}
}
}
Permission Flow:
graph TD
A[Start Navigation] --> B{Has Permissions?}
B -->|Yes| C[Start Tracking]
B -->|No| D[Show Rationale]
D --> E[Request Permissions]
E --> F{Granted?}
F -->|Yes| C
F -->|No| G{Permanently Denied?}
G -->|Yes| H[Show Settings Dialog]
G -->|No| I[Show Retry Option]
H --> J[Open Settings]
I --> E
C --> K[Navigation Active]
Permission Utilities:
// Quick permission checks
fun checkLocationPermission(): Boolean {
return PermissionManager.hasPermission(context, PermissionType.LOCATION_FINE)
}
// Request with callback
PermissionManager.request(
activity = this,
permissions = listOf(PermissionType.LOCATION_FINE)
) { granted, denied ->
if (granted.isNotEmpty()) {
startNavigation()
}
}
// Check if should show rationale
if (PermissionManager.shouldShowRationale(activity, PermissionType.LOCATION_FINE)) {
showCustomRationaleDialog()
}
Bug Fixes¶
User Navigation Display Issue¶
Issue: Navigation UI components occasionally failed to display correctly, particularly: - Instruction panel showing blank text - Route overlay not rendering on map - Progress bar stuck at 0% - Navigation compass showing incorrect bearing
Root Cause: - Race condition between UI initialization and navigation start - Improper view lifecycle management - Memory corruption in route data structure - Threading issues with UI updates from background location callbacks
Resolution:
// Fixed UI initialization sequence
class NavigationUIManager(private val sceneView: MineSceneView) {
private val uiComponents = mutableListOf<NavigationUIComponent>()
private val uiHandler = Handler(Looper.getMainLooper())
fun initializeUI() {
// Ensure UI initialization on main thread
uiHandler.post {
uiComponents.forEach { component ->
component.initialize(sceneView)
component.setVisibility(View.GONE)
}
}
}
fun startNavigation(route: Route) {
// Wait for UI initialization
if (!isUIReady()) {
waitForUI { startNavigation(route) }
return
}
// Update UI on main thread
uiHandler.post {
uiComponents.forEach { component ->
component.setVisibility(View.VISIBLE)
component.setRoute(route.copy()) // Use defensive copy
}
}
}
// Thread-safe location updates
fun updateLocation(location: Location) {
uiHandler.post {
uiComponents.forEach { component ->
component.onLocationUpdate(location)
}
}
}
}
Impact: - 100% UI display success rate (up from 92%) - Eliminated flickering and blank screens - Reduced navigation startup time by 35%
POI Interaction Memory Leak¶
Issue: Critical memory leak when interacting with POIs during active navigation: - Memory usage gradually increased over time - App crashes after 15-20 POI interactions - Garbage collector unable to reclaim memory - OutOfMemoryError on long navigation sessions
Root Cause: - POI click listeners not being unregistered - Circular references between POI objects and UI components - Bitmap resources not being released - Navigation context holding references to destroyed activities
Resolution:
// Implemented proper cleanup
class POIInteractionManager(private val sceneView: MineSceneView) {
private val activeListeners = WeakHashMap<String, POIClickListener>()
private val bitmapCache = LruCache<String, Bitmap>(4 * 1024 * 1024) // 4MB
fun registerPOI(poi: POI) {
// Use weak references for listeners
val listener = POIClickListener { clickedPOI ->
handlePOIClick(clickedPOI)
}
activeListeners[poi.id] = listener
poi.setClickListener(listener)
}
fun unregisterPOI(poiId: String) {
activeListeners.remove(poiId)?.let { listener ->
// Clean up listener
listener.cleanup()
}
}
fun clearAll() {
// Clean up all listeners
activeListeners.values.forEach { it.cleanup() }
activeListeners.clear()
// Clear bitmap cache
bitmapCache.evictAll()
}
// Automatically cleanup on activity destroy
fun onDestroy() {
clearAll()
}
}
// Proper lifecycle management
class NavigationActivity : AppCompatActivity() {
private lateinit var poiManager: POIInteractionManager
override fun onDestroy() {
poiManager.onDestroy()
super.onDestroy()
}
}
Memory Improvements:
| Scenario | Before | After | Improvement |
|---|---|---|---|
| 10 POI interactions | 145MB | 78MB | โฌ๏ธ 46% |
| 30 min navigation | 380MB | 125MB | โฌ๏ธ 67% |
| Peak memory | 512MB | 185MB | โฌ๏ธ 64% |
| Memory leaks | Yes | None | โ Fixed |
Location Tracking Update Issues¶
Issue: Location tracking had several update-related problems: - Position updates delayed by 2-3 seconds - Jumpy or erratic location markers - Location not updating when user stationary - Battery drain from excessive updates
Root Cause: - Inefficient sensor data processing - No smoothing or filtering applied - Update interval too aggressive - Missing optimization for stationary detection
Resolution:
// Implemented Kalman filter and optimization
class OptimizedLocationTracker(context: Context) : LocationTracker(context) {
private val kalmanFilter = KalmanFilter()
private var lastLocation: Location? = null
private var isStationary = false
override fun processLocationUpdate(rawLocation: Location) {
// Apply Kalman filter for smooth tracking
val filteredLocation = kalmanFilter.filter(rawLocation)
// Detect stationary state
lastLocation?.let { last ->
val distance = filteredLocation.distanceTo(last)
isStationary = distance < STATIONARY_THRESHOLD
}
// Adjust update frequency based on motion
if (isStationary) {
setUpdateInterval(STATIONARY_UPDATE_INTERVAL) // 5 seconds
} else {
setUpdateInterval(MOVING_UPDATE_INTERVAL) // 1 second
}
// Only update if position changed significantly
if (shouldUpdateLocation(filteredLocation)) {
notifyLocationUpdate(filteredLocation)
lastLocation = filteredLocation
}
}
private fun shouldUpdateLocation(location: Location): Boolean {
lastLocation?.let { last ->
val distance = location.distanceTo(last)
val timeDelta = location.time - last.time
// Update if moved enough or enough time passed
return distance > MIN_UPDATE_DISTANCE || timeDelta > MAX_UPDATE_DELAY
}
return true // First update
}
companion object {
const val STATIONARY_THRESHOLD = 1.0f // meters
const val STATIONARY_UPDATE_INTERVAL = 5000L // ms
const val MOVING_UPDATE_INTERVAL = 1000L // ms
const val MIN_UPDATE_DISTANCE = 0.5f // meters
const val MAX_UPDATE_DELAY = 2000L // ms
}
}
Performance Improvements:
- Update latency: 2.8s โ 0.3s (โฌ๏ธ 89%)
- Location smoothness: 6/10 โ 9/10 score
- Battery consumption: โฌ๏ธ 42%
- Update accuracy: โฌ๏ธ 38%
Navigation Instructions Display Issue¶
Issue: Turn-by-turn instructions had display problems: - Instructions appearing too late (after turn) - Duplicate instructions shown - Text cutoff on small screens - Wrong instruction for current position
Resolution: - Implemented distance-based instruction triggers with proper thresholds - Added instruction deduplication logic - Implemented responsive text sizing - Enhanced position-to-instruction matching algorithm
Before:
// Instructions triggered at fixed time
if (timeToTurn < 5) {
showInstruction(nextInstruction)
}
After:
// Distance and context-aware triggers
fun shouldShowInstruction(instruction: NavigationInstruction, userLocation: Location): Boolean {
val distanceToTurn = calculateDistance(userLocation, instruction.location)
val userSpeed = locationTracker.getCurrentSpeed()
// Calculate dynamic trigger distance based on speed
val triggerDistance = when {
userSpeed > 2.0f -> 30f // Running: 30m warning
userSpeed > 1.0f -> 20f // Walking: 20m warning
else -> 10f // Slow: 10m warning
}
return distanceToTurn <= triggerDistance && !instruction.isShown
}
Known Issues¶
Scene Rendering on Low-End Devices¶
Performance Issue
Severity: Medium
Platforms: Devices with < 2GB RAM or Mali-400 GPU
Status: Optimization in progress
Description: On low-end Android devices, the 3D scene rendering may experience performance degradation during active navigation, particularly when: - Multiple UI overlays are displayed simultaneously - Route visualization with many waypoints - Real-time location marker updates - Floor transitions with animations
Impact: - Frame rate drops to 20-30 FPS (target: 60 FPS) - Occasional stuttering during route drawing - Increased battery consumption - Delayed response to user interactions
Affected Device Examples: - Samsung Galaxy J series - Xiaomi Redmi 6A/7A - Generic Android devices with Mali-400 GPU - Devices with 1-2GB RAM
Current Workarounds:
// Optimize for low-end devices
if (DeviceUtil.isLowEndDevice()) {
sceneView.apply {
// Reduce render quality
displayConfig = DisplayConfig(
renderQuality = DisplayConfig.RenderQuality.LOW,
shadowsEnabled = false,
ambientOcclusion = false,
antiAliasing = false
)
// Limit frame rate
maxFrameRate = 30
// Simplify route visualization
routeRenderConfig = RouteRenderConfig(
segmentCount = 20, // Reduce from default 50
smoothing = false,
animationsEnabled = false
)
// Reduce location update frequency
locationUpdateInterval = 2000 // 2 seconds instead of 1
}
// Use 2D mode for better performance
sceneView.setRenderMode(RenderMode.MODE_2D)
}
fun DeviceUtil.isLowEndDevice(): Boolean {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memoryInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memoryInfo)
return memoryInfo.totalMem < 2L * 1024 * 1024 * 1024 // Less than 2GB
}
Recommended Approach: - Enable 2D mode for low-end devices automatically - Reduce UI overlay complexity - Use simplified route visualization - Increase location update intervals
Status: We're actively working on rendering optimizations for v0.5.0, including: - Adaptive quality system that auto-adjusts based on device capability - Improved GPU utilization - Reduced overdraw - Better memory management
Upgrade from v0.3.2¶
Breaking Changes¶
API Changes
This release includes some API changes that require code updates.
Navigation Initialization¶
Before (v0.3.2):
val navigationHelper = NavigationHelper(context)
navigationHelper.startNavigation(destination)
After (v0.4.0):
val navigationManager = UserNavigationManager(context, sceneView)
navigationManager.startNavigation(destination, options)
Location Listener¶
Before:
sceneView.setLocationListener { location ->
// Handle location
}
After:
locationTracker.setLocationListener(object : LocationListener {
override fun onLocationChanged(location: Location) {
// Handle location
}
})
Migration Steps¶
- Update Dependency
=== "Gradle (Kotlin)"
```kotlin
dependencies {
implementation("com.machinestalk:indoornavigationengine:0.4.0-alpha")
}
```
=== "Gradle (Groovy)"
```groovy
dependencies {
implementation 'com.machinestalk:indoornavigationengine:0.4.0-alpha'
}
```
-
Sync and Clean Build
./gradlew clean build -
Update Navigation Code
// Replace NavigationHelper with UserNavigationManager val navigationManager = UserNavigationManager(this, sceneView) // Use new navigation API navigationManager.startNavigation( destination = destinationLocation, options = NavigationOptions(voiceGuidance = true) ) -
Update Location Tracking
// Initialize location tracker val locationTracker = LocationTracker(this) locationTracker.setLocationListener(object : LocationListener { override fun onLocationChanged(location: Location) { sceneView.updateUserLocation(location) } }) -
Add Permission Handling
val permissionManager = PermissionManager(this) permissionManager.requestPermissions( listOf(PermissionType.LOCATION_FINE) ) { granted, denied -> if (granted.isNotEmpty()) { locationTracker.startTracking() } }
New Feature Integration¶
// Take advantage of new features
class ModernNavigationActivity : AppCompatActivity() {
private lateinit var navigationManager: UserNavigationManager
private lateinit var locationTracker: LocationTracker
private lateinit var permissionManager: PermissionManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val sceneView = MineSceneView(this)
setContentView(sceneView)
// Setup navigation
navigationManager = UserNavigationManager(this, sceneView).apply {
setNavigationMode(NavigationMode.WALKING)
setReroutingConfig(ReroutingConfig(enabled = true))
}
// Setup location tracking
locationTracker = LocationTracker(this).apply {
addProvider(LocationProvider.WIFI)
addProvider(LocationProvider.BLUETOOTH)
setUpdateInterval(1000)
}
// Setup permissions
permissionManager = PermissionManager(this).apply {
requirePermission(PermissionType.LOCATION_FINE, required = true)
}
// Add navigation UI
sceneView.addUIComponent(LiveNavigationPanel(this))
sceneView.addUIComponent(NavigationCompass(this))
}
}
Testing & Quality¶
Test Coverage¶
| Component | Unit Tests | Integration | Coverage | Status |
|---|---|---|---|---|
| User Navigation | โ 145 tests | โ 52 tests | 93% | Pass |
| Location Tracking | โ 98 tests | โ 38 tests | 91% | Pass |
| Instructions | โ 87 tests | โ 29 tests | 88% | Pass |
| Permissions | โ 56 tests | โ 22 tests | 95% | Pass |
| Navigation UI | โ 102 tests | โ 41 tests | 89% | Pass |
Real-World Testing¶
| Scenario | Tests | Success Rate | Avg. Accuracy |
|---|---|---|---|
| Shopping mall | 50 | 98% | 2.3m |
| Airport terminal | 35 | 96% | 2.8m |
| Hospital | 28 | 97% | 2.1m |
| Office building | 42 | 99% | 1.9m |
| Museum | 22 | 98% | 2.5m |
Device Coverage¶
| Category | Models | Pass Rate | Notes |
|---|---|---|---|
| Flagship | 18 | 100% โ | Excellent |
| Mid-range | 25 | 98% โ | Good |
| Budget | 20 | 90% โ ๏ธ | Performance issues |
| Tablets | 15 | 100% โ | Optimal |
Performance Metrics¶
Navigation Performance:
| Metric | Value | Target | Status |
|---|---|---|---|
| Navigation Start Time | 0.8s | < 1s | โ |
| Reroute Calculation | 0.4s | < 0.5s | โ |
| Location Update Latency | 0.3s | < 0.5s | โ |
| Instruction Display Lag | 0.1s | < 0.2s | โ |
| Battery Drain (1hr nav) | 8% | < 10% | โ |
What's Next - v0.5.0¶
Exciting features planned for the next release:
Confirmed Features¶
- ๐บ๏ธ Offline Map Packages - Download and use maps without internet
- ๐ Voice Guidance - Full text-to-speech navigation
- ๐ Navigation Analytics - Detailed usage insights
- ๐ฏ Smart Suggestions - AI-powered destination recommendations
- โก Performance Boost - 50% faster rendering on low-end devices
- ๐ Multi-language - Support for 15+ languages
Under Consideration¶
- ๐ฎ AR Navigation - Augmented reality wayfinding
- ๐ Vehicle Navigation - Support for wheelchairs, carts
- ๐ฅ Group Navigation - Navigate with friends
- ๐ท Visual Positioning - Camera-based positioning
- ๐ต Audio Beacons - Ultrasonic positioning
Timeline¶
Expected Release: Q4 2024
Beta Program: October 2024
Feature Preview: September 2024
Complete Implementation Example¶
Here's a comprehensive example showcasing all navigation features:
package com.example.indoornavapp
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.machinestalk.indoornavigationengine.ui.MineSceneView
import com.machinestalk.indoornavigationengine.navigation.*
import com.machinestalk.indoornavigationengine.location.*
import com.machinestalk.indoornavigationengine.permissions.*
import com.machinestalk.indoornavigationengine.ui.navigation.*
class FullNavigationActivity : AppCompatActivity() {
private lateinit var sceneView: MineSceneView
private lateinit var navigationManager: UserNavigationManager
private lateinit var locationTracker: LocationTracker
private lateinit var permissionManager: PermissionManager
// UI Components
private lateinit var navigationPanel: LiveNavigationPanel
private lateinit var compass: NavigationCompass
private lateinit var speedDisplay: SpeedETADisplay
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize scene
sceneView = MineSceneView(this)
setContentView(sceneView)
// Load map
initializeMap()
// Setup components
setupPermissions()
setupLocationTracking()
setupNavigation()
setupUI()
}
private fun initializeMap() {
val mapData = JsonUtil.LoadJsonFromAsset(this, "maps/venue.json")
mapData?.let { sceneView.setMapData(it) }
}
private fun setupPermissions() {
permissionManager = PermissionManager(this).apply {
requirePermission(
PermissionType.LOCATION_FINE,
required = true,
rationale = "Required for navigation"
)
setPermissionCallback(object : PermissionCallback {
override fun onPermissionsGranted(permissions: List<PermissionType>) {
locationTracker.startTracking()
}
override fun onPermissionsDenied(
permissions: List<PermissionType>,
permanentlyDenied: List<PermissionType>
) {
if (permanentlyDenied.isNotEmpty()) {
showPermissionSettingsDialog()
}
}
})
}
}
private fun setupLocationTracking() {
locationTracker = LocationTracker(this).apply {
// Add providers
addProvider(LocationProvider.WIFI, priority = 10)
addProvider(LocationProvider.BLUETOOTH, priority = 8)
addProvider(LocationProvider.SENSOR_FUSION, priority = 6)
// Configure
setMinAccuracy(5f)
setUpdateInterval(1000)
// Listen for updates
setLocationListener(object : LocationListener {
override fun onLocationChanged(location: Location) {
handleLocationUpdate(location)
}
override fun onAccuracyChanged(accuracy: Float) {
updateAccuracyIndicator(accuracy)
}
})
}
}
private fun setupNavigation() {
navigationManager = UserNavigationManager(this, sceneView).apply {
setNavigationMode(NavigationMode.WALKING)
setReroutingConfig(ReroutingConfig(
enabled = true,
deviationThreshold = 5f,
autoRerouteDelay = 2000
))
setNavigationListener(object : NavigationListener {
override fun onNavigationStarted(route: Route) {
showNavigationUI()
}
override fun onInstructionChanged(instruction: NavigationInstruction) {
navigationPanel.showInstruction(instruction)
}
override fun onArrival() {
showArrivalDialog()
hideNavigationUI()
}
override fun onNavigationError(error: NavigationError) {
handleNavigationError(error)
}
})
}
}
private fun setupUI() {
navigationPanel = LiveNavigationPanel(this).apply {
setPosition(LiveNavigationPanel.Position.TOP)
}
compass = NavigationCompass(this).apply {
setPosition(NavigationCompass.Position.TOP_LEFT)
}
speedDisplay = SpeedETADisplay(this).apply {
setPosition(SpeedETADisplay.Position.BOTTOM_LEFT)
}
sceneView.addUIComponent(navigationPanel)
sceneView.addUIComponent(compass)
sceneView.addUIComponent(speedDisplay)
}
fun navigateTo(destinationId: String) {
permissionManager.checkAndRequestPermissions {
val destination = sceneView.getPOIById(destinationId)
destination?.let {
navigationManager.startNavigation(
destination = it.location,
options = NavigationOptions(
voiceGuidance = true,
vibrationFeedback = true,
showProgressBar = true
)
)
}
}
}
private fun handleLocationUpdate(location: Location) {
sceneView.updateUserLocation(location)
navigationManager.updateUserLocation(location)
compass.updateLocation(location)
speedDisplay.updateSpeed(location.speed)
}
override fun onDestroy() {
navigationManager.destroy()
locationTracker.stopTracking()
super.onDestroy()
}
}
Feedback & Support¶
Your feedback helps us improve!
-
Report Issues
Found a problem? Let us know!
-
Feature Requests
Have ideas for navigation features?
-
Get Help
Need assistance with navigation?
-
Share Feedback
Tell us about your experience
Additional Resources¶
- ๐งญ Navigation Features - Complete navigation guide
- ๐ Path Finding - Advanced routing techniques
- ๐จ UI Components - UI component reference
- ๐ Quick Start - Get started quickly
- ๐ป Usage Guide - Comprehensive examples
- ๐ API Reference - Complete API docs
- โ FAQ - Common questions
- ๐ Troubleshooting - Problem solving
Acknowledgments¶
Special thanks to all beta testers, early adopters, and contributors who helped make this release possible!
License¶
This software is released under the commercial license. Please review the license terms before use.
๐ Ready to Navigate!
Version 0.4.0 brings the complete indoor navigation experience to your users. Start building amazing navigation applications today!