Skip to content

Latest commit

 

History

History
410 lines (241 loc) · 17.9 KB

File metadata and controls

410 lines (241 loc) · 17.9 KB

Map Layers Rework — Tracking Plan

This tracking document breaks the redesign into concrete, code-aware tasks with checkboxes. It’s organized into phases to keep the app working while we migrate gradually. Larger items are split into small, actionable tasks tied to specific files/classes.

Legend

  • = Todo
  • = Done

Process

  • After completing (and ticking) each checkbox or small sub-step, create a focused commit describing exactly what changed (e.g., "map: fix MapEventListener OnMapClick wiring" or "map: add lifecycle observer for MapSensorController"). Keep commits small and logically isolated to ease review and potential rollback.

Phase 1 — Critical fixes and stabilizers

Stabilize current map to reduce risk before introducing v2.

1.1 MapEventListener click handler bug

Files: map/src/main/java/com/adsamcik/tracker/map/MapEventListener.kt

  • Fix incorrect plus/minus assign conditions for OnMapClickListener.
  • Add unit test verifying add/remove wiring and isolation of other listeners.

Acceptance

  • Adding/removing last OnMapClickListener properly sets/unsets GoogleMap.onMapClickListener.
  • No unrelated listeners are affected by click listener removal.

1.2 Rotation/bearing updates resume and lifecycle hygiene

Files: map/.../MapSensorController.kt, map/.../fragment/FragmentMap.kt, map/.../MapOwner.kt

  • Add lifecycle observer (onStart/onStop) for MapSensorController.
  • Register/unregister sensors + location in onStart/onStop.
  • Connect observer from FragmentMap when map is ready.

1.2.a Consolidate lifecycle authority (follow-up)

Currently both MapOwner enable/disable listeners and the lifecycle observer could trigger enable/disable. Consolidated to lifecycle observer only.

  • Choose lifecycle observer as single authority.
  • Remove MapOwner enable/disable wiring for sensors.
  • Add idempotence guard + debug log.

Acceptance

  • Only one code path invokes MapSensorController enable/disable (verified via log).
  • No duplicate sensor or location requests after rapid pause/resume cycles.
  • Following mode resumes after app resume and cancels on user camera move (test + manual QA).

Additional Verification

  • No retained SensorManager callbacks after onStop (LeakCanary clean).
  • Following/bearing resume reliably with permission granted.

1.3 Centralize zoom constants

Files: map/.../MapController.kt, map/.../heatmap/HeatmapTileProvider.kt

  • Create map/src/main/java/com/adsamcik/tracker/map/MapConstants.kt with const val MAX_ZOOM = 17f.
  • Replace MapController.MAX_ZOOM usages with MapConstants.MAX_ZOOM in:
    • MapController (setMaxZoomPreference)
    • HeatmapTileProvider (MAX_HEAT_ZOOM)
  • Remove MAX_ZOOM from MapController.Companion when references are updated.
    • Also updated additional usages in heatmap tile creators (not originally listed) to ensure compilation.

Acceptance

  • Build compiles and runtime behavior unchanged.

Phase 2 — Core architecture scaffolding (v2; no behavior change yet)

Introduce state/registry alongside existing implementation.

2.1 Map state and ViewModel

Files (new): map/src/main/java/com/adsamcik/tracker/map/v2/presentation/MapViewModel.kt

  • Create MapViewModel with StateFlow holding immutable MapState:
    • selectedLayerId, dateRange, quality, followMode, userLocation, searchQuery, searchResults, isLoading, error, bottomSheetState, layerParameters, tileGenerationProgress.
  • Add debounced refresh utility inside ViewModel for parameter changes.

Acceptance

  • ViewModel compiles; can be instantiated in tests.

2.2 Layer descriptors and registry (manual, no DI yet)

Files (new): smap/.../shared/map/v2/layers/{LayerDescriptor.kt, LayerCapabilities.kt, LayerRecipe.kt, LayerParameter.kt} Files (new): map/.../v2/layers/registry/{LayerRegistry.kt, DefaultLayerRegistry.kt}

  • Define LayerDescriptor, LayerCapabilities, LayerRecipe, LayerParameter, LayerFactory in shared smap to be accessible by other modules.
  • Implement LayerRegistry interface and DefaultLayerRegistry with manual registration.
  • Register descriptors mirroring current layers from MapSheetController list:
    • NoMap, Location Heatmap, Cell Heatmap, Wifi Heatmap, Wifi Count Heatmap, Location Polyline, Speed Heatmap.
    • For now, factories can return wrapper adapters to existing MapLayerLogic implementations (v1) to keep parity.

Acceptance

  • DefaultLayerRegistry.getAllLayers() returns descriptors for all current layers with names/icons wired to existing resources.

2.3 MapHost and LayerController scaffolding

Files (new): map/.../v2/ui/{MapHost.kt, LayerController.kt}

  • MapHost: lifecycle-safe wrapper around SupportMapFragment using MapOwner patterns; exposes mapReady observable.
  • LayerController: minimal wrapper that enables/disables a selected layer (using descriptor factory) on the map.
  • Do not change UI yet; keep v1 MapSheetController active.

Acceptance

  • Able to initialize MapHost from FragmentMap without disturbing v1 flow. (Compilation-only scaffold; wiring deferred to 2.4.)

2.4 Wire ViewModel/MapHost lightly in FragmentMap (no behavior change)

Files: map/.../fragment/FragmentMap.kt

  • Instantiate MapViewModel and MapHost in onViewCreated (behind a dev flag or no-op collectors).
  • Observe ViewModel state but don’t drive UI yet.

Acceptance

  • App runs as before; no visual changes.

Phase 3 — Data layer increments (Room flexible queries)

Add flexible query support without refactoring all DAOs.

3.1 Unified DAO with raw queries

Files (new): shared.base.database/.../dao/UnifiedGeoDao.kt (module where AppDatabase lives)

  • Add @RawQuery(observedEntities=[LocationData::class]) fun queryLocations(q: SupportSQLiteQuery): Flow<List<GeoFeatureEntity>>. (Implemented as DatabaseLocation observed; wifi/cell placeholders commented.)
  • Add similar for Wifi and Cell entities if available: WifiData, CellLocation.
  • Add weighted variants returning GeoWeightedFeatureEntity (lat, lon, time, weight) for location, wifi, cell.
  • Ensure AppDatabase exposes the new DAO.

3.2 GeoFeatureEntity and converters

Files (new): shared.base.database/.../entity/GeoFeatureEntity.kt

  • GeoFeatureEntity(lat: Double, lon: Double, time: Long, properties: Map<String, Double>) stored as JSON (map currently used for future queries; empty by default).
  • Introduce @TypeConverter to serialize/deserialize the map; verify no conflicting converters.

3.3 SafeQueryBuilder

Files (new): map/.../v2/data/SafeQueryBuilder.kt

  • Implement safe, parameterized SQL builder with allowed columns per data source; support bounds and optional time range.
  • Unit tests for invalid columns/predicates.

3.4 GeoRepository

Files (new): map/.../v2/data/GeoRepositoryImpl.kt

  • Implement GeoRepository.query(query: GeoQuery): Flow<List<GeoFeature>> using UnifiedGeoDao and SafeQueryBuilder (supports weighted queries).
  • Implement queryWeighted(query, weightColumn) convenience API returning WeightedGeoFeature list.
  • Add client-side grid aggregation (queryWeightedAggregated) with Sum/Avg/Max strategies.
  • Implement queryWeighted(...) for tiled/weighted use-cases.

3.5 Optional: use repo in one tile creator under flag

Files: map/.../heatmap/creators/LocationHeatmapTileCreator.kt

  • Introduce dev flag to fetch via GeoRepository for a small, controlled scenario (Location heatmap); falls back to DAO when disabled.

Acceptance

  • Unit tests green for builder and repository; v1 creators still work.

Phase 4 — Performance guardrails

Make performance limits explicit and centralized.

4.1 PerformanceManager

Files (new): map/.../v2/perf/PerformanceManager.kt

  • Provide budgets by quality (Low/Medium/High): maxPoints, maxPolylinePoints, maxCacheSize, tileRenderTimeout, decimationThreshold, batchSize.

4.2 BitmapPool

Files (new): map/.../v2/graphics/BitmapPool.kt

  • Implement simple bounded pool with acquire(width,height) and release(bitmap).

4.3 PolylineOptimizer

Files (new): map/.../v2/graphics/PolylineOptimizer.kt

  • Implement Douglas-Peucker and even spacing; API (points, tolerance, maxPoints) -> List<LatLng>.

4.4 HeatmapTileProvider improvements (non-breaking)

Files: map/.../heatmap/HeatmapTileProvider.kt

  • Add optional injection/setter for BitmapPool.
  • Add in-memory LRU tile cache (keyed by x,y,zoom) with bounded size from PerformanceManager.
  • Enforce per-tile render timeout to avoid long stalls; fail fast to NO_TILE on timeout.
  • Review locking around tile generation; reduce contention to render-critical sections only.
  • Add low-memory trim path (clear tile cache + bitmap pool via FragmentMap.onLowMemory).

Acceptance

  • Manual smoke: panning/zooming heavy areas shows no visible jank; median tile render < 100ms on dev device.

Phase 5 — Layer system v2 and first migrations

Introduce base classes and migrate layers incrementally.

5.1 Base layer abstractions

Files (new): map/.../v2/layers/base/{BaseMapLayer.kt, HeatmapLayer.kt}

  • Implement template method: enable() calls beforeEnable -> loadData -> processData -> render -> afterEnable.
  • Integrate PerformanceManager in processing.

5.2 Optimized tile provider

Files (new): map/.../v2/tiles/OptimizedTileProvider.kt

  • Implement tile provider using BitmapPool, LRU cache, and safe rendering.

5.3 Migrate Location Heatmap first

Files: create new LocationHeatmapLayer in map/.../v2/layers/impl/LocationHeatmapLayer.kt

  • Use GeoRepository for data load; grid aggregation for performance. (Scaffolded; tile provider stub returns NO_TILE until full render path is wired.)
  • Render via OptimizedTileProvider. (Provider created, quality propagation wired.)
  • Provide descriptor with parameters (date range, quality, etc.). (Deferred to 5.4 where the UI reads v2 descriptors.)

5.4 UI: descriptors-driven layer list (dual path)

Files: map/.../MapSheetController.kt

  • Add new adapter (or branch) that renders from LayerRegistry descriptors.
  • Keep existing hard-coded list for fallback; guard with dev toggle.
  • When a descriptor is selected, use LayerController to enable v2 layer.

Acceptance

  • Location Heatmap works through v2 flow without regressions; switching layers tears down overlays cleanly.

Notes

  • A dev flag USE_V2_LAYER_LIST was added to guard the new list; default remains legacy.
  • Registry currently adapts to v1 MapLayerLogic factories to avoid behavior change; wiring v2 LocationHeatmapLayer under a flag can follow.
  • Follow-ups (nice-to-have): persist last-selected descriptor ID for v2 list; mirror tile-generation counter UI for v2 LayerController path.

5.5 Migrate remaining layers

Files:

  • WifiHeatmapLogic -> WifiHeatmapLayer

  • WifiCountHeatmapLogic -> WifiCountHeatmapLayer

  • CellHeatmapLogic -> CellHeatmapLayer

  • SpeedHeatmapLogic -> SpeedHeatmapLayer

  • LocationPolylineLogic -> LocationPathLayer (with decimation)

  • Implement per-layer recipes and descriptors; port legends and colors from MapLayerData (via v2 adapters; direct v2 descriptor props to follow in cleanup).

  • Quick visual QA on overlays and legends (parity is not required, just acceptable visuals).

Acceptance

  • All v1 MapLayerLogic features are available in v2 with acceptable visuals. Cleanup/removal will proceed in 5.6 without strict parity.

5.6 Cutover and cleanup (move from 6.4)

Purpose: simplify codebase by making the new layer system the only path and completely removing legacy.

Files: map/.../MapSheetController.kt, map/.../ui/LayerController.kt, map/.../layers/registry/DefaultLayerRegistry.kt, smap/.../shared/map/MapLayerLogic.kt, smap/.../shared/map/MapLayer.kt, heatmap creators/providers if unused

  • Make descriptors list the default; remove legacy v1 list branch.
  • Replace descriptor factories to return new layers directly (drop adapters casting to MapLayerLogic).
  • Update LayerController to hold and control new layer types only.
  • Migrate legend/title/icon/colors into descriptors; stop reading from v1 layer data.
  • Remove adapters in map/layers/adapters/* (empty directories removed).
  • COMPLETE REMOVAL: Remove MapLayerLogic interface entirely (no backwards compatibility).
  • COMPLETE REMOVAL: Remove MapLayer compatibility interface.
  • Remove legacy heatmap tile creators/providers that are no longer used.
  • Remove dev flags around v2 path and delete obsolete toggles.
  • Drop the "v2" naming: move packages from .../map/v2/... to .../map/..., and rename classes to neutral names. Update imports/usages accordingly.
  • COMPLETE CUTOVER: Update MapLayerInfo to use string class names instead of Class references.
  • Compile + smoke test map layer switching and overlays.

Acceptance

  • App uses new layer system only; build green; overlays mount/unmount; NO backwards compatibility - all legacy v1 references completely removed.

Phase 6 — Testing and cleanup

6.1 Unit tests

Files: map/src/test/...

  • MapViewModelTest: selection, param updates, debounced refresh.
  • SafeQueryBuilderTest: allowed columns, parameterization, predicate safety.
  • PerformanceManagerTest: budget selection.
  • PolylineOptimizerTest: reductions to within budget and tolerance.

Note

  • During this phase, release unit tests for the map module are skipped to avoid R8/minify issues with mocks. A follow-up will re-enable them with appropriate keep rules.

6.2 Tile harness and visual regression (optional)

Files (new): map/.../v2/test/tiles/TileTestHarness.kt

  • Introduce headless tile harness utility TileTestHarness (unit tests) for exercising OptimizedTileProvider.
  • Add minimal smoke test using a fake provider to validate harness wiring (no golden comparison yet).
  • Golden image comparisons with tolerance for selected tiles/layers (enable later via Robolectric or move to androidTest).
  • Notes: Harness currently avoids Robolectric to keep CI lean; golden tests deferred.

6.3 Integration tests

  • Layer switch smoke test ensuring overlays mount/unmount and legends update.
    • Added LayerSwitchIntegrationTest under androidTest with a fake layer and descriptor wiring; verifies render callback on switch and legend changes via LayerController.

6.4 Remove v1 code

  • Eliminate v1 usage from map flow: LayerController and DefaultLayerRegistry now use v2 layers only; legends come from descriptors.
  • Physical deletion of legacy v1 interfaces and unused heatmap pipeline classes:
    • Deleted: smap/shared/map/MapLayerLogic.kt, MapLayer.kt
    • Deleted: map/heatmap/HeatmapTileProvider.kt
    • Deleted: map/heatmap/creators/ directory and all legacy creator classes
    • Retained: minimal support classes (HeatmapConfig, HeatmapTileData, WiFi constants) needed by v2 layers
  • Updated v2 layer imports and fixed compilation issues after cleanup.
  • COMMITTED: Changes committed to git with cleanup documentation (30 files changed, 165 insertions, 952 deletions)
  • CONNECTED TEST: LayerSwitchIntegrationTest passed on Medium_Phone_API_36.0(AVD) - layer switching works correctly after cleanup
  • DEPRECATION CLEANUP: Removed deprecated JCenter repository references from build files - no more deprecation warnings

Acceptance

  • App uses v2 path by default; map and app modules assemble; unit tests pass for map module.
  • Connected Android test passes, confirming layer switching functionality works in runtime environment.

Codebase research notes (grounding)

  • Fragment and controllers
    • FragmentMap: sets up MapController, MapSensorController, and MapSheetController; uses MapOwner lifecycle hooks.
    • MapOwner: abstracts map creation and enable/disable events.
    • MapController: manages active MapLayerLogic; MAX_ZOOM = 17f used and referenced by HeatmapTileProvider.
    • MapSensorController: handles sensors and location updates; currently no lifecycle observer.
    • MapSheetController: hard-codes layer list; controls legend and bottom sheet, search/geocoder.
  • Layers v1
    • MapLayerLogic interface in smap/.../shared/map/MapLayerLogic.kt with onEnable/onDisable/update, availableRange/dateRange/quality, and tileCountInGeneration LiveData.
    • Heatmaps implemented via HeatmapLayerLogic base class and tile creators (LocationHeatmapTileCreator, WifiHeatmapTileCreator, CellHeatmapTileCreator, SpeedHeatmapTileCreator).
    • LocationPolylineLogic uses DAOs (LocationDataDao, SessionDataDao) to draw polylines.
  • Heatmap provider
    • HeatmapTileProvider implements tile generation with internal concurrency and a heat-change mechanism; references MapController.MAX_ZOOM as MAX_HEAT_ZOOM.
  • Known bug
    • MapEventListener.minusAssign(OnMapClickListener) incorrectly checks onCameraMoveListeners and unsets OnCameraIdleListener instead of OnMapClickListener.

Open questions / assumptions

  • DI: No Hilt/Dagger present. We’ll start with manual DefaultLayerRegistry; Hilt multibindings can be adopted later without blocking progress.
  • DAOs: LocationDataDao, SessionDataDao referenced in map module; their definitions live in the shared database module (verify paths before implementing UnifiedGeoDao).
  • Feature flags: Prefer simple build-time or runtime toggles to switch between v1 and v2 during migration.

Success metrics checklist

  • Extensibility: Add a new layer by registering a descriptor only (no core edits).
  • Performance: Median tile render < 100ms under normal datasets; no visible jank when panning/zooming.
  • Quality: ≥80% unit test coverage for new v2 code; 0 critical leaks (LeakCanary).
  • Parity: v2 covers all existing layers and features, including legends.