Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9389be3
Expose trip schedule distances and add geometry utilities
bmander Apr 13, 2026
8b7df63
Modernize AnimationUtil and extract StampedPolylineFactory
bmander Apr 13, 2026
32b43ac
Add probability distribution library and Polyline utility
bmander Apr 13, 2026
e887b54
Add extrapolation engine with gamma and schedule-replay strategies
bmander Apr 13, 2026
0951f73
Add TripDataManager, coroutine-based pollers, and ThrottledFrameLoop
bmander Apr 13, 2026
c6e987b
Extract VehicleMapController and animate markers between data updates
bmander Apr 13, 2026
8461e50
Add trip map view with route overlay and distance estimate visualization
bmander Apr 13, 2026
856ff67
Wire up trip details UI, data views, and speed estimation UI
bmander Apr 14, 2026
2f38f97
Merge main into animated-markers
bmander Jun 4, 2026
262d5b0
Split TripDataManager into TripStore and TripFetcher with permanent T…
bmander Jun 4, 2026
dadd5c1
Slim TripStore to identity, retention, and writes; read Trips directly
bmander Jun 4, 2026
ff14df8
Port TripFetcher to coroutines and consolidate one-shot fetches
bmander Jun 4, 2026
ba93ea3
Make trip resource fetchers pure suspend functions with SingleFlight …
bmander Jun 4, 2026
074b184
Dissolve TripStore into a functional module backed by ShellRegistry
bmander Jun 4, 2026
5d6dd30
Migrate trip data layer to immutable TripState snapshots
bmander Jun 6, 2026
d4b4126
Replace ShellRegistry with a plain LruCache of TripState snapshots
bmander Jun 6, 2026
3f69d20
Apply review cleanups to the LruCache store conversion
bmander Jun 6, 2026
6a5741a
Lift the NaN-quantile contract from bisect to the ProbDistribution in…
bmander Jun 6, 2026
6716d9d
Pass the bearing, not the polyline, to updateDirectionIcon
bmander Jun 6, 2026
374cb74
Rename updatePosition(s) to updateVehicleMarker(s)
bmander Jun 6, 2026
1241d92
Extract response adapters from TripStore into a standard TripObservation
bmander Jun 6, 2026
6ad992b
Rename the TripState folds into the with* family
bmander Jun 6, 2026
9c5a8c6
Resolve routeType in the trip details adapter
bmander Jun 6, 2026
96af019
Cache trip details responses even when they carry no vehicle status
bmander Jun 6, 2026
6b5490a
Rethrow CancellationException in the poll loops
bmander Jun 6, 2026
6c64c52
Ignore VS Code and Eclipse project files
bmander Jun 6, 2026
31bb4de
Seal trip store writes inside the data package
bmander Jun 6, 2026
7915fb7
Apply simplify-pass cleanups to the trip data layer
bmander Jun 6, 2026
4a0b003
Gather TripStore's functions into an object
bmander Jun 6, 2026
147f733
Add adapter tests for TripObservation distillation
bmander Jun 6, 2026
6e222de
Scope TripMapFragment startup to the view lifecycle
bmander Jun 6, 2026
dba51f7
Group urimap.json fixture routes by endpoint
bmander Jun 6, 2026
1414e1e
Run the vehicle frame loop only while the map is shown
bmander Jun 6, 2026
8cd9db4
Pin strategy re-selection on late routeType in TripStateTest
bmander Jun 6, 2026
a568ee3
Narrow the per-frame extrapolation catches to IllegalArgumentException
bmander Jun 6, 2026
b221dfd
Surface trip poll failures and back off the poll cadence
bmander Jun 6, 2026
1b9bd15
Apply review fixes to the poll-failure surfacing
bmander Jun 6, 2026
aaa0e37
Use expression-body form for the two fetch-and-record functions
bmander Jun 6, 2026
5178cdf
Apply the quick-win batch from PR #1554 review items 10, 14, 15, 17, 18
bmander Jun 6, 2026
54801af
Distinguish retryable trip map activation failures
bmander Jun 6, 2026
ec8820b
Sweep the remaining CodeRabbit inline review items
bmander Jun 6, 2026
0bb532f
Address CodeRabbit's second-pass review comments
bmander Jun 6, 2026
0f3bd18
Bump Jackson 2.12.4 -> 2.13.5 and clear deprecated Jackson API usage
bmander Jun 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
settings.local.json
.kotlin/
play-store-upload-key.json
*.keystore
*.keystore
.vscode/
.project
.classpath
bin/
17 changes: 13 additions & 4 deletions onebusaway-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ android {
isDefault = true
buildConfigField "String", "MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.googlemapsv2.BaseMapFragment\""
buildConfigField "String", "MAP_HELPER_CLASS", "\"org.onebusaway.android.map.googlemapsv2.GoogleProprietaryMapHelper\""
buildConfigField "String", "TRIP_MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.googlemapsv2.tripmap.TripMapFragment\""
}

maplibre {
// MapLibre-based build using OpenFreeMap tiles - see src/maplibre
dimension "platform"
buildConfigField "String", "MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.maplibre.MapLibreMapFragment\""
buildConfigField "String", "MAP_HELPER_CLASS", "\"org.onebusaway.android.map.maplibre.MapLibreProprietaryMapHelper\""
buildConfigField "String", "TRIP_MAP_FRAGMENT_CLASS", "\"org.onebusaway.android.map.maplibre.tripmap.TripMapFragment\""
}

// Brand flavors are loaded from separate files in flavors/ directory.
Expand Down Expand Up @@ -238,9 +240,11 @@ dependencies {
// Open311 client library
implementation 'com.github.OneBusAway:open311-client:1.0.10'
// JSON data binding for OBA REST API responses
implementation 'com.fasterxml.jackson.core:jackson-core:2.12.4'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.12.4'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.4'
// NOTE: 2.13.x is the last Jackson line supporting Android SDK 21+ (2.14+ requires SDK 26,
// and core library desugaring does not help) - do not bump further while minSdk is 21
implementation 'com.fasterxml.jackson.core:jackson-core:2.13.5'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.13.5'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.5'
// Bottom sliding panel
implementation 'com.github.hannesa2:AndroidSlidingUpPanel:4.6.1'
// For floating action button speed dial
Expand All @@ -265,7 +269,9 @@ dependencies {
implementation 'com.google.android.material:material:1.12.0'
// Explicit dependency on Gson is now apparently needed
implementation 'com.google.code.gson:gson:2.10.1'
// Unit tests - seems like this is still necessary w/ Android X even though useLibrary is declared earlier
// JVM unit tests
testImplementation 'junit:junit:4.13.2'
// Instrumented tests - seems like this is still necessary w/ Android X even though useLibrary is declared earlier
androidTestImplementation 'androidx.test:runner:1.6.2'
// WorkManager (Java only)
implementation 'androidx.work:work-runtime:2.9.1'
Expand All @@ -279,6 +285,9 @@ dependencies {
implementation "androidx.room:room-runtime:2.7.0"
ksp "androidx.room:room-compiler:2.7.0"
implementation "androidx.room:room-ktx:2.7.0"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
// GTFS Realtime bindings for parsing GTFS-realtime data
implementation "org.mobilitydata:gtfs-realtime-bindings:0.0.8"
implementation 'com.google.api.grpc:proto-google-common-protos:2.9.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2024-2026 Open Transit Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onebusaway.android.extrapolation.test

import androidx.test.InstrumentationRegistry.getTargetContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Test
import org.onebusaway.android.app.Application
import org.onebusaway.android.extrapolation.data.toObservations
import org.onebusaway.android.io.request.ObaTripDetailsRequest
import org.onebusaway.android.io.request.ObaTripsForRouteRequest
import org.onebusaway.android.io.test.ObaTestCase
import org.onebusaway.android.mock.MockRegion

/**
* Tests the response adapters (Adapters.kt) against mock API responses from /res/raw: the
* distillation of trip-details and trips-for-route responses into TripObservations, including
* the skip guards for responses without a status or an active trip ID.
*/
class AdaptersTest : ObaTestCase() {

companion object {
private const val HART_TRIP_ID = "Hillsborough Area Regional Transit_1389962"
private const val HART_ROUTE_ID = "Hillsborough Area Regional Transit_5"
}

// --- ObaTripDetailsResponse.toObservations ---

@Test
fun detailsResponseDistillsOneObservation() {
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
val response =
ObaTripDetailsRequest.Builder(getTargetContext(), HART_TRIP_ID).build().call()
assertOK(response)

val observations = response.toObservations()
assertEquals(1, observations.size)

val observation = observations[0]
assertEquals(HART_TRIP_ID, observation.tripId)
assertSame(response.status, observation.status)
assertEquals(response.currentTime, observation.serverTimeMs)
assertEquals(1545886800000L, observation.serviceDate)
// Resolved through the refs: trip 1389962 -> route 16 -> type 3 (bus)
assertEquals(3, observation.routeType ?: -1)
}

@Test
fun detailsResponseWithoutStatusYieldsNoObservations() {
// Puget Sound fixture: schedule and refs present, no vehicle status
val response =
ObaTripDetailsRequest.Builder(getTargetContext(), "1_18196913").build().call()
assertOK(response)
assertNull(response.status)

assertTrue(response.toObservations().isEmpty())
}

@Test
fun detailsResponseWithoutActiveTripIdYieldsNoObservations() {
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
val response =
ObaTripDetailsRequest.Builder(
getTargetContext(),
"${HART_TRIP_ID}_no_active_trip"
)
.build()
.call()
assertOK(response)
// Status present but the vehicle reports no active trip (e.g. between runs)
assertNotNull(response.status)
assertNull(response.status!!.activeTripId)

assertTrue(response.toObservations().isEmpty())
}

// --- ObaTripsForRouteResponse.toObservations ---

@Test
fun tripsForRouteResponseDistillsOneObservationPerActiveTrip() {
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
val response =
ObaTripsForRouteRequest.Builder(getTargetContext(), HART_ROUTE_ID)
.setIncludeStatus(true)
.build()
.call()
assertOK(response)

val observations = response.toObservations()
assertEquals(38, observations.size)

val first = observations[0]
assertEquals("Hillsborough Area Regional Transit_101446", first.tripId)
assertEquals(response.currentTime, first.serverTimeMs)
assertEquals(1444017600000L, first.serviceDate)
assertEquals(3, first.routeType ?: -1)

// Each observation is keyed by the trip its vehicle reported as active
for (observation in observations) {
assertEquals(observation.status.activeTripId, observation.tripId)
}
}

@Test
fun tripsForRouteResponseWithoutStatusesYieldsNoObservations() {
Application.get().setCurrentRegion(MockRegion.getTampa(getTargetContext()))
val response =
ObaTripsForRouteRequest.Builder(getTargetContext(), HART_ROUTE_ID)
.build()
.call()
assertOK(response)

assertTrue(response.toObservations().isEmpty())
}
}
Loading
Loading