Skip to content

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

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)
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%

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.

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

  1. 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'
   }
   ```
  1. Sync and Clean Build

    ./gradlew clean build
    

  2. Update Navigation Code

    // Replace NavigationHelper with UserNavigationManager
    val navigationManager = UserNavigationManager(this, sceneView)
    
    // Use new navigation API
    navigationManager.startNavigation(
        destination = destinationLocation,
        options = NavigationOptions(voiceGuidance = true)
    )
    

  3. Update Location Tracking

    // Initialize location tracker
    val locationTracker = LocationTracker(this)
    locationTracker.setLocationListener(object : LocationListener {
        override fun onLocationChanged(location: Location) {
            sceneView.updateUserLocation(location)
        }
    })
    

  4. 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

Join Beta Program


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!


Additional Resources


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!