From 8d170a82896a3a6336b727866bd8c4c0082cf1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 08:43:46 -0400 Subject: [PATCH 01/15] GTFS-RT > Tests... --- .../provider/GTFSRealTimeProvider.java | 8 +- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 1 + .../commons/provider/gtfs/GtfsRealtimeExt.kt | 2 + .../GTFSRealTimeServiceAlertsProvider.kt | 9 +- .../status/GTFSRealTimeTripUpdatesProvider.kt | 16 +- .../GTFSRealTimeVehiclePositionsProvider.kt | 4 +- .../GTFSRealTimeServiceAlertsProviderTest.kt | 141 ++++++++++++++++++ 7 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java index e940cca9..fef406ef 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -892,7 +892,6 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context final String sourceLabel = SourceUtils.getSourceLabel( // always use source from official API getAgencyServiceAlertsUrlString(context, "T") ); - final boolean ignoreDirection = isIGNORE_DIRECTION(context); try { GtfsRealtime.FeedMessage gFeedMessage = GtfsRealtime.FeedMessage.parseFrom(response.body().bytes()); List> alertsWithIdPair = GtfsRealtimeExt.toAlertsWithIdPair(gFeedMessage.getEntityList()); @@ -903,7 +902,7 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context if (Constants.DEBUG) { MTLog.d(this, "loadAgencyServiceUpdateDataFromWWW() > GTFS - [%s] %s", feedEntityId, GtfsRealtimeExt.toStringExt(gAlert)); } - final Set alertsServiceUpdates = processAlerts(context, sourceLabel, feedEntityId, newLastUpdateInMs, gAlert, ignoreDirection); + final Set alertsServiceUpdates = processAlerts(context, sourceLabel, feedEntityId, newLastUpdateInMs, gAlert); if (alertsServiceUpdates != null && !alertsServiceUpdates.isEmpty()) { serviceUpdates.addAll(alertsServiceUpdates); } @@ -968,8 +967,7 @@ private HashSet processAlerts( @NonNull String sourceLabel, @Nullable String feedEntityId, long newLastUpdateInMs, - GtfsRealtime.Alert gAlert, - boolean ignoreDirection + GtfsRealtime.Alert gAlert ) { if (gAlert == null) return null; java.util.List gInformedEntityList = gAlert.getInformedEntityList(); @@ -993,7 +991,7 @@ private HashSet processAlerts( MTLog.w(this, "processAlerts() > Alert targets another agency: %s", gInformedEntity.getAgencyId()); continue; } - final String targetUUID = GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID(this, gInformedEntity, ignoreDirection); + final String targetUUID = GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID(this, gInformedEntity); if (targetUUID == null || targetUUID.isEmpty()) { continue; } diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 273c3f5f..79ea597d 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -34,6 +34,7 @@ import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelecto import com.google.transit.realtime.GtfsRealtime.TripDescriptor as GTripDescriptor import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTUStopTimeUpdate +val Number.isValidDirection get() = this in 0..1 val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(requireContextCompat()) val GTFSRealTimeProvider.timeZone get() = getAGENCY_TIME_ZONE(requireContextCompat()) diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt index 1ab44a81..859ac322 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealtimeExt.kt @@ -471,6 +471,7 @@ object GtfsRealtimeExt { val GEntitySelector.optAgencyId get() = if (hasAgencyId()) agencyId else null val GEntitySelector.optRouteId get() = if (hasRouteId()) routeId else this.optTrip?.optRouteId val GEntitySelector.optDirectionId get() = if (hasDirectionId()) directionId else this.optTrip?.optDirectionId + val GEntitySelector.optDirectionIdValid get() = optDirectionId?.takeIf { it.isValidDirection } val GEntitySelector.optStopId get() = if (hasStopId()) stopId else null val GEntitySelector.optRouteType get() = if (hasRouteType()) routeType else null val GEntitySelector.optTrip get() = if (hasTrip()) this.trip else null @@ -495,6 +496,7 @@ object GtfsRealtimeExt { val GTripDescriptor.optTripId get() = if (hasTripId()) tripId else optModifiedTrip?.optAffectedTripId val GTripDescriptor.optRouteId get() = if (hasRouteId()) routeId else null val GTripDescriptor.optDirectionId get() = if (hasDirectionId()) directionId else null + val GTripDescriptor.optDirectionIdValid get() = optDirectionId?.takeIf { it.isValidDirection } val GTripDescriptor.optModifiedTrip get() = if (hasModifiedTrip()) modifiedTrip else null val GTripDescriptor.optScheduleRelationship get() = if (hasScheduleRelationship()) scheduleRelationship else null val GTripDescriptor.optStartDate get() = if (hasStartDate()) startDate else null diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 30a05bc9..e7b022bf 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -14,6 +14,7 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopT import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optAgencyId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionIdValid import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteType import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt @@ -75,10 +76,14 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { fun GTFSRealTimeProvider.parseTargetTripId(gEntitySelector: GEntitySelector) = gEntitySelector.optTrip?.let { parseTripId(it) } + @JvmOverloads @JvmStatic - fun GTFSRealTimeProvider.parseProviderTargetUUID(gEntitySelector: GEntitySelector, ignoreDirection: Boolean): String? { + fun GTFSRealTimeProvider.parseProviderTargetUUID( + gEntitySelector: GEntitySelector, + ignoreDirection: Boolean = this.ignoreDirection, + ): String? { parseRouteId(gEntitySelector)?.let { routeId -> - gEntitySelector.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> + gEntitySelector.optDirectionIdValid?.takeIf { !ignoreDirection }?.let { directionId -> parseStopId(gEntitySelector)?.let { stopId -> return getAgencyRouteDirectionStopTagTargetUUID(agencyTag, routeId, directionId, stopId) } // no stop diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index 4d877a05..b9ec0d0a 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -16,7 +16,7 @@ import org.mtransit.android.commons.data.toNoData import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isIGNORE_DIRECTION import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage -import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionIdValid import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.sortTripUpdates import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt @@ -130,13 +130,19 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { gTripUpdate.optTrip?.let { it to gTripUpdate } }.filter { (td, _) -> parseTripId(td)?.let { tripId -> - if (tripId !in tripIds) return@filter false + if (tripId !in tripIds) { + return@filter false + } } parseRouteId(td)?.let { routeIdHash -> - if (routeIdHash != targetRouteIdHash) return@filter false + if (routeIdHash != targetRouteIdHash) { + return@filter false + } } - td.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> - if (directionId != targetDirectionOriginalId) return@filter false + td.optDirectionIdValid?.takeIf { !ignoreDirection }?.let { directionId -> + if (directionId != targetDirectionOriginalId) { + return@filter false + } } return@filter true }.takeIf { it.isNotEmpty() } diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index 2339eab5..3cc41ab5 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -11,7 +11,7 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optBearing -import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId +import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionIdValid import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optLabel import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optLatitude @@ -291,7 +291,7 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { -> MTLog.d(LOG_TAG, "parseTargetUUID() > unhandled schedule relationship: ${gTripDescriptor.scheduleRelationship}") } parseRouteId(gTripDescriptor)?.let { routeId -> - gTripDescriptor.optDirectionId?.takeIf { !ignoreDirection }?.let { directionId -> + gTripDescriptor.optDirectionIdValid?.takeIf { !ignoreDirection }?.let { directionId -> return getAgencyRouteDirectionTagTargetUUID(agencyTag, routeId, directionId) } return getAgencyRouteTagTargetUUID(agencyTag, routeId) diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt new file mode 100644 index 00000000..b2f33a2a --- /dev/null +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -0,0 +1,141 @@ +package org.mtransit.android.commons.provider.serviceupdate + +import com.google.transit.realtime.entitySelector +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mtransit.android.commons.provider.GTFSRealTimeProvider +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteStopTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTypeTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID +import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID +import org.mtransit.commons.CommonsApp +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class GTFSRealTimeServiceAlertsProviderTest { + + private val gtfsRealTimeProvider: GTFSRealTimeProvider = mock { + on { getAgencyTag(anyOrNull()) } doReturn "static_agency_id" + } + + @BeforeTest + fun setUp() { + CommonsApp.setup(false) + } + + @Test + fun test_parseProviderTargetUUID() { + // EMPTY + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector {}, + ignoreDirection = false + ).let { result -> + assertNull(result) + } + // AGENCY only + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + agencyId = "agency_id" + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyTagTargetUUID("static_agency_id"), result) + } + // ROUTE + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyRouteTagTargetUUID("static_agency_id", stringIdToHash("route_id")), result) + } + // ROUTE + DIRECTION + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + directionId = 0 + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyRouteDirectionTagTargetUUID("static_agency_id", stringIdToHash("route_id"), 0), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + directionId = 777777777 // INVALID! + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyRouteTagTargetUUID("static_agency_id", stringIdToHash("route_id")), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + directionId = 0 + }, + ignoreDirection = true + ).let { result -> + assertEquals(getAgencyRouteTagTargetUUID("static_agency_id", stringIdToHash("route_id")), result) + } + // ROUTE + DIRECTION + STOP + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + directionId = 0 + stopId = "stop_id" + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyRouteDirectionStopTagTargetUUID("static_agency_id", stringIdToHash("route_id"), 0, stringIdToHash("stop_id")), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + directionId = 0 + stopId = "stop_id" + }, + ignoreDirection = true + ).let { result -> + assertEquals(getAgencyRouteStopTagTargetUUID("static_agency_id", stringIdToHash("route_id"), stringIdToHash("stop_id")), result) + } + // ROUTE + STOP + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeId = "route_id" + stopId = "stop_id" + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyRouteStopTagTargetUUID("static_agency_id", stringIdToHash("route_id"), stringIdToHash("stop_id")), result) + } + // STOP only + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + stopId = "stop_id" + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyStopTagTargetUUID("static_agency_id", stringIdToHash("stop_id")), result) + } + // ROUTE TYPE + gtfsRealTimeProvider.parseProviderTargetUUID( + gEntitySelector = entitySelector { + routeType = 3 // bus + }, + ignoreDirection = false + ).let { result -> + assertEquals(getAgencyRouteTypeTagTargetUUID("static_agency_id", 3), result) + } + } + + private fun stringIdToHash(originalId: String) = originalId.hashCode().toString() + +} From be7df6b0856e632fce7c38e5251c22e432b82946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 11:06:28 -0400 Subject: [PATCH 02/15] wip --- .../android/commons/data/DefaultPOI.java | 55 +++++++------ .../android/commons/data/Direction.java | 24 ++++++ .../mtransit/android/commons/data/Route.java | 43 +++++----- .../android/commons/data/RouteDirection.java | 17 ++++ .../commons/data/RouteDirectionStop.java | 25 ++++++ .../android/commons/data/ServiceUpdate.java | 6 ++ .../mtransit/android/commons/data/Stop.java | 28 +++++++ .../GTFSRealTimeServiceAlertsProvider.kt | 22 ++++-- .../data/RouteDirectionStopTestFixtures.kt | 58 ++++++++++++++ .../commons/data/ServiceUpdateTestFixtures.kt | 53 +++++++++++++ .../GTFSRealTimeServiceAlertsProviderTest.kt | 78 +++++++++++++++++++ 11 files changed, 360 insertions(+), 49 deletions(-) create mode 100644 src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt create mode 100644 src/test/java/org/mtransit/android/commons/data/ServiceUpdateTestFixtures.kt diff --git a/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java b/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java index 197eb7fa..0a796758 100644 --- a/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java +++ b/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java @@ -67,30 +67,37 @@ public DefaultPOI(@NonNull String authority, int id, @DataSourceType int dataSou @Override public boolean equals(Object o) { - if (o == null) { - return false; - } - if (this.getClass() != o.getClass()) { - return false; - } - DefaultPOI otherPOI = (DefaultPOI) o; - if (!this.getUUID().equals(otherPOI.getUUID())) { - return false; - } - if (this.getType() != otherPOI.getType()) { - return false; - } - if (this.getStatusType() != otherPOI.getStatusType()) { - return false; - } - if (this.getActionsType() != otherPOI.getActionsType()) { - return false; - } - //noinspection RedundantIfStatement - if (!Objects.equals(this.getName(), otherPOI.getName())) { - return false; - } - return true; + if (!(o instanceof DefaultPOI)) return false; + final DefaultPOI that = (DefaultPOI) o; + return id == that.id + && authority.equals(that.authority) + && name.equals(that.name) + && Double.compare(lat, that.lat) == 0 + && Double.compare(lng, that.lng) == 0 + && accessible == that.accessible + && type == that.type + && dataSourceTypeId == that.dataSourceTypeId + && statusType == that.statusType + && actionsType == that.actionsType + && Objects.equals(scoreOpt, that.scoreOpt) + ; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + id; + result = 31 * result + authority.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + Double.hashCode(lat); + result = 31 * result + Double.hashCode(lng); + result = 31 * result + accessible; + result = 31 * result + type; + result = 31 * result + dataSourceTypeId; + result = 31 * result + statusType; + result = 31 * result + actionsType; + result = 31 * result + Objects.hashCode(scoreOpt); + return result; } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/data/Direction.java b/src/main/java/org/mtransit/android/commons/data/Direction.java index 4b388707..aa1f5d79 100644 --- a/src/main/java/org/mtransit/android/commons/data/Direction.java +++ b/src/main/java/org/mtransit/android/commons/data/Direction.java @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.util.Comparator; +import java.util.Objects; @SuppressWarnings("WeakerAccess") public class Direction implements Targetable { @@ -273,6 +274,29 @@ public long getRouteId() { return routeId; } + @Override + public boolean equals(Object o) { + if (!(o instanceof Direction)) return false; + final Direction direction = (Direction) o; + return id == direction.id + && authority.equals(direction.authority) + && headsignType == direction.headsignType + && headsignValue.equals(direction.headsignValue) + && routeId == direction.routeId + ; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + Long.hashCode(id); + result = 31 * result + authority.hashCode(); + result = 31 * result + headsignType; + result = 31 * result + headsignValue.hashCode(); + result = 31 * result + Long.hashCode(routeId); + return result; + } + public static class HeadSignComparator implements Comparator, MTLog.Loggable { private static final String LOG_TAG = Direction.class.getSimpleName() + ">" + HeadSignComparator.class.getSimpleName(); diff --git a/src/main/java/org/mtransit/android/commons/data/Route.java b/src/main/java/org/mtransit/android/commons/data/Route.java index 42cbc80d..45ad469a 100644 --- a/src/main/java/org/mtransit/android/commons/data/Route.java +++ b/src/main/java/org/mtransit/android/commons/data/Route.java @@ -117,24 +117,31 @@ public int getColorInt() { @Override public boolean equals(Object o) { - if (!(o instanceof Route)) { - return false; - } - Route otherRoute = (Route) o; - if (getId() != otherRoute.getId()) { - return false; - } - if (!Objects.equals(getShortName(), otherRoute.getShortName())) { - return false; - } - if (!Objects.equals(getLongName(), otherRoute.getLongName())) { - return false; - } - //noinspection RedundantIfStatement - if (!Objects.equals(getColor(), otherRoute.getColor())) { - return false; - } - return true; + if (!(o instanceof Route)) return false; + final Route route = (Route) o; + return id == route.id + && authority.equals(route.authority) + && shortName.equals(route.shortName) + && longName.equals(route.longName) + && color.equals(route.color) + && Objects.equals(originalIdHash, route.originalIdHash) + && Objects.equals(type, route.type) + && Objects.equals(colorInt, route.colorInt) + ; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + Long.hashCode(id); + result = 31 * result + authority.hashCode(); + result = 31 * result + shortName.hashCode(); + result = 31 * result + longName.hashCode(); + result = 31 * result + color.hashCode(); + result = 31 * result + Objects.hashCode(originalIdHash); + result = 31 * result + Objects.hashCode(type); + result = 31 * result + Objects.hashCode(colorInt); + return result; } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirection.java b/src/main/java/org/mtransit/android/commons/data/RouteDirection.java index edb0db84..e7f803c0 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirection.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirection.java @@ -59,6 +59,23 @@ public boolean equals(int routeId, int directionIdId) { return getRoute().getId() == routeId && getDirection().getId() == directionIdId; } + @Override + public boolean equals(Object o) { + if (!(o instanceof RouteDirection)) return false; + final RouteDirection that = (RouteDirection) o; + return route.equals(that.route) + && direction.equals(that.direction) + ; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + route.hashCode(); + result = 31 * result + direction.hashCode(); + return result; + } + @NonNull @Override public String toString() { diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index 170e6152..9601460a 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Objects; public class RouteDirectionStop extends DefaultPOI { @@ -154,6 +155,30 @@ public boolean equals(int routeId, int directionIdId, int stopId) { return getRoute().getId() == routeId && getDirection().getId() == directionIdId && getStop().getId() == stopId; } + @Override + public boolean equals(Object o) { + if (!(o instanceof RouteDirectionStop)) return false; + if (!super.equals(o)) return false; + final RouteDirectionStop that = (RouteDirectionStop) o; + return route.equals(that.route) + && direction.equals(that.direction) + && stop.equals(that.stop) + && noPickup == that.noPickup + && Objects.equals(alwaysLastTripStop, that.alwaysLastTripStop) + ; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + route.hashCode(); + result = 31 * result + direction.hashCode(); + result = 31 * result + stop.hashCode(); + result = 31 * result + Boolean.hashCode(noPickup); + result = 31 * result + Objects.hashCode(alwaysLastTripStop); + return result; + } + @NonNull @Override public String toString() { diff --git a/src/main/java/org/mtransit/android/commons/data/ServiceUpdate.java b/src/main/java/org/mtransit/android/commons/data/ServiceUpdate.java index c226f881..a034826a 100644 --- a/src/main/java/org/mtransit/android/commons/data/ServiceUpdate.java +++ b/src/main/java/org/mtransit/android/commons/data/ServiceUpdate.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import org.mtransit.android.commons.ComparatorUtils; import org.mtransit.android.commons.CursorExtKt; @@ -258,6 +259,11 @@ public boolean isUseful() { return this.lastUpdateInMs + this.maxValidityInMs >= TimeUtils.currentTimeMillis(); } + @VisibleForTesting + protected long getMaxValidityInMs() { + return maxValidityInMs; + } + @Nullable public Integer getId() { return this.id; diff --git a/src/main/java/org/mtransit/android/commons/data/Stop.java b/src/main/java/org/mtransit/android/commons/data/Stop.java index 5b188e58..f19ac093 100644 --- a/src/main/java/org/mtransit/android/commons/data/Stop.java +++ b/src/main/java/org/mtransit/android/commons/data/Stop.java @@ -15,6 +15,8 @@ import org.mtransit.android.commons.provider.GTFSProviderContract; import org.mtransit.commons.GTFSCommons; +import java.util.Objects; + @SuppressWarnings("WeakerAccess") public class Stop { @@ -209,4 +211,30 @@ public boolean isSameOriginalId(@Nullable String cleanedOriginalIdHash) { return this.originalIdHash.toString().equals(cleanedOriginalIdHash); } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Stop)) return false; + final Stop stop = (Stop) o; + return id == stop.id + && code.equals(stop.code) + && name.equals(stop.name) + && Double.compare(lat, stop.lat) == 0 + && Double.compare(lng, stop.lng) == 0 + && accessible == stop.accessible + && Objects.equals(originalIdHash, stop.originalIdHash); + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + id; + result = 31 * result + code.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + Double.hashCode(lat); + result = 31 * result + Double.hashCode(lng); + result = 31 * result + accessible; + result = 31 * result + Objects.hashCode(originalIdHash); + return result; + } } diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index e7b022bf..c3f6229b 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -13,7 +13,6 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optAgencyId -import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionIdValid import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteType import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip @@ -45,20 +44,29 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { } targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // trip IDs not required for GTFS Alerts }?.let { (targetUUIDs, tripIds) -> - getCached(filter, targetUUIDs, tripIds) + getCached(filter, targetUUIDs, tripIds, getCachedServiceUpdates = { targetUUIDs, tripIds -> + getCachedServiceUpdatesS(targetUUIDs, tripIds) + }) } - fun GTFSRealTimeProvider.getCached(filter: ServiceUpdateProviderContract.Filter, targetUUIDs: Map, tripIds: List?) = buildList { + fun GTFSRealTimeProvider.getCached( + filter: ServiceUpdateProviderContract.Filter, + targetUUIDs: Map, + tripIds: List?, + getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List?, + ignoreDirection: Boolean = this.ignoreDirection, + ) = buildList { ( // 1 - trip IDs preferred for all result filtered correctly - tripIds?.let { getCachedServiceUpdatesS(targetUUIDs.keys, tripIds = it) }?.takeIf { it.isNotEmpty() } + tripIds?.let { getCachedServiceUpdates(targetUUIDs.keys, it) }?.takeIf { it.isNotEmpty() } // 2 - fallback to: ignore TRIP IDS (outdated?) and try using primary target UUID only // - only works if Route & Direction! provided // -> can NOT show service alerts for the wrong direction ?: if (ignoreDirection) null - else filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = false, includeStopTags = true)?.let { (providerTargetUUID, _) -> - getCachedServiceUpdatesS(setOf(providerTargetUUID), tripIds = null) - }?.takeIf { it.isNotEmpty() } + else filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = false, includeStopTags = true) + ?.let { (providerTargetUUID, _) -> + getCachedServiceUpdates(setOf(providerTargetUUID), null) + }?.takeIf { it.isNotEmpty() } ) ?.let { addAll(it) diff --git a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt new file mode 100644 index 00000000..2399b54b --- /dev/null +++ b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt @@ -0,0 +1,58 @@ +package org.mtransit.android.commons.data + +import org.mtransit.android.commons.provider.GTFSRealTimeProvider + +fun makeRDS( + authority: String = "authority", + routeId: Long = 1L, + routeOriginalIdHash: Int? = routeId.toString().hashCode(), + routeType: Int = 3, + originalDirectionId: Int? = 1, + directionId: Long = originalDirectionId?.let { routeId * 100L + it } ?: (routeId * 100L + 9L), + stopId: Int = 1, + stopOriginalIdHash: Int? = stopId.toString().hashCode() // stopId, // "$stopId".hashCode() +) = RouteDirectionStop( + 1, + Route( + authority, + routeId, + "#$routeId", + "route $routeId", + "color", + routeOriginalIdHash, + routeType, + ), + Direction( + authority, + directionId, + Direction.HEADSIGN_TYPE_STRING, + "Head-Sign $originalDirectionId", + routeId, + ), + Stop( + stopId, + "#$stopId", + "Stop #$stopId", + 1.0, + 2.0, + Accessibility.DEFAULT, + stopOriginalIdHash, + ), + false, + false, +) + +fun RouteDirectionStop.getGTFSRTTargetUUID(): String = + GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID( + authority, + route.originalIdHash.toString(), + direction.originalDirectionIdOrNull, + stop.originalIdHashString + ) ?: GTFSRealTimeProvider.getAgencyRouteStopTagTargetUUID( + authority, + route.originalIdHash.toString(), + stop.originalIdHashString + ) ?: GTFSRealTimeProvider.getAgencyRouteTagTargetUUID( + authority, + route.originalIdHash.toString() + ) diff --git a/src/test/java/org/mtransit/android/commons/data/ServiceUpdateTestFixtures.kt b/src/test/java/org/mtransit/android/commons/data/ServiceUpdateTestFixtures.kt new file mode 100644 index 00000000..a212da80 --- /dev/null +++ b/src/test/java/org/mtransit/android/commons/data/ServiceUpdateTestFixtures.kt @@ -0,0 +1,53 @@ +package org.mtransit.android.commons.data + +import org.mtransit.android.commons.TimeUtilsK +import org.mtransit.android.commons.toMillis +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Instant + +fun makeServiceUpdate( + optId: Int? = null, + targetUUID: String = "uuid", + targetTripId: String? = null, + lastUpdate: Instant = TimeUtilsK.currentInstant(), + maxValidity: Duration = 1.hours, + text: String = "The text", + optTextHTML: String? = null, + severity: Int = ServiceUpdate.SEVERITY_NONE, + noService: Boolean? = null, + sourceId: String = "source_id", + sourceLabel: String = "example.org", + originalId: String? = null, + language: String = "en" +) = makeServiceUpdate( + optId = optId, + targetUUID = targetUUID, + targetTripId = targetTripId, + lastUpdateMs = lastUpdate.toMillis(), + maxValidityMs = maxValidity.inWholeMilliseconds, + text = text, + optTextHTML = optTextHTML, + severity = severity, + noService = noService, + sourceId = sourceId, + sourceLabel = sourceLabel, + originalId = originalId, + language = language, +) + +fun ServiceUpdate.clone() = makeServiceUpdate( + optId = id, + targetUUID = targetUUID, + targetTripId = targetTripId, + lastUpdateMs = lastUpdateInMs, + maxValidityMs = maxValidityInMs, + text = text, + optTextHTML = textHTML, + severity = severity, + noService = isNoService, + sourceId = sourceId, + sourceLabel = sourceLabel, + originalId = originalId, + language = language +) diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index b2f33a2a..795d3321 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -3,7 +3,15 @@ package org.mtransit.android.commons.provider.serviceupdate import com.google.transit.realtime.entitySelector import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import org.mtransit.android.commons.data.RouteDirectionStop +import org.mtransit.android.commons.data.ServiceUpdate +import org.mtransit.android.commons.data.clone +import org.mtransit.android.commons.data.getGTFSRTTargetUUID +import org.mtransit.android.commons.data.makeRDS +import org.mtransit.android.commons.data.makeServiceUpdate import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionTagTargetUUID @@ -12,11 +20,14 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTypeTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID +import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs +import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.getCached import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID import org.mtransit.commons.CommonsApp import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNotNull import kotlin.test.assertNull class GTFSRealTimeServiceAlertsProviderTest { @@ -30,6 +41,73 @@ class GTFSRealTimeServiceAlertsProviderTest { CommonsApp.setup(false) } + @Test + fun test_getCached() { + val rds1 = makeRDS( + authority = "static_agency_id", + routeId = 1L, + originalDirectionId = 1, + stopId = 10, + ) + setupProviderForRDS(rds1) + val rds2 = makeRDS( + authority = "static_agency_id", + routeId = 2L, + originalDirectionId = 0, + stopId = 20, + ) + setupProviderForRDS(rds2) + val cachedServiceUpdates = buildList { + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId10")) + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId11")) + } + val getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> + cachedServiceUpdates.filter { targetUUIDs.contains(it.targetUUID) && tripIds?.contains(it.targetTripId) != false }.map { it.clone() } + } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds1), + targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), + tripIds = listOf("tripId10"), + getCachedServiceUpdates = getCachedServiceUpdates, + ignoreDirection = false + ).let { result -> + assertEquals(1, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.uuid, it.targetUUID) + } + } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds1), + targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), + tripIds = listOf("tripId177777"), // out-of-sync (static!=real-time) + getCachedServiceUpdates = getCachedServiceUpdates, + ignoreDirection = false + ).let { result -> + assertEquals(2, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.uuid, it.targetUUID) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { + assertEquals(rds1.uuid, it.targetUUID) + } + } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds2), + targetUUIDs = rds2.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), + tripIds = listOf("tripId22"), + getCachedServiceUpdates = getCachedServiceUpdates, + ignoreDirection = false + ).let { result -> + assertEquals(0, result.size) + } + } + + private fun setupProviderForRDS(rds: RouteDirectionStop) { + whenever { gtfsRealTimeProvider.getRouteTag(eq(rds.route)) } doReturn rds.route.originalIdHash.toString() + whenever { gtfsRealTimeProvider.getDirectionTag(eq(rds.direction)) } doReturn rds.direction.originalDirectionIdOrNull + whenever { gtfsRealTimeProvider.getStopTag(eq(rds.stop)) } doReturn rds.stop.originalIdHashString + } + @Test fun test_parseProviderTargetUUID() { // EMPTY From a093824b1a992758de31522740dc65f719cfe60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 11:15:16 -0400 Subject: [PATCH 03/15] ++ --- .../GTFSRealTimeServiceAlertsProviderTest.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index 795d3321..3a2f0367 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -91,6 +91,15 @@ class GTFSRealTimeServiceAlertsProviderTest { assertEquals(rds1.uuid, it.targetUUID) } } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds1), + targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), + tripIds = listOf("tripId177777"), // out-of-sync (static!=real-time) + getCachedServiceUpdates = getCachedServiceUpdates, + ignoreDirection = true + ).let { result -> + assertEquals(0, result.size) + } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds2), targetUUIDs = rds2.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), From 7b06f2d27b49addc53306017c01f2a6827330723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 11:27:20 -0400 Subject: [PATCH 04/15] wip --- .../data/RouteDirectionStopTestFixtures.kt | 4 +- .../GTFSRealTimeServiceAlertsProviderTest.kt | 71 ++++++++++++++++--- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt index 2399b54b..2488cd94 100644 --- a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt +++ b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt @@ -42,11 +42,11 @@ fun makeRDS( false, ) -fun RouteDirectionStop.getGTFSRTTargetUUID(): String = +fun RouteDirectionStop.getGTFSRTTargetUUID(ignoreDirection: Boolean = false): String = GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID( authority, route.originalIdHash.toString(), - direction.originalDirectionIdOrNull, + direction.originalDirectionIdOrNull?.takeIf { !ignoreDirection }, stop.originalIdHashString ) ?: GTFSRealTimeProvider.getAgencyRouteStopTagTargetUUID( authority, diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index 3a2f0367..3d580f6b 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -58,11 +58,16 @@ class GTFSRealTimeServiceAlertsProviderTest { ) setupProviderForRDS(rds2) val cachedServiceUpdates = buildList { - add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId10")) - add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId11")) + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId10", text = "Text 10")) + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId11", text = "Text 11")) + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(ignoreDirection = true), targetTripId = "tripId12", text = "Text 12")) + add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = null, text = "Text 00")) } val getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> - cachedServiceUpdates.filter { targetUUIDs.contains(it.targetUUID) && tripIds?.contains(it.targetTripId) != false }.map { it.clone() } + cachedServiceUpdates.filter { serviceUpdate -> + targetUUIDs.contains(serviceUpdate.targetUUID) + && serviceUpdate.targetTripId?.let { tripIds?.contains(it) } != false // ignore if target tripID or local trip ID null + }.map { it.clone() } } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), @@ -71,24 +76,60 @@ class GTFSRealTimeServiceAlertsProviderTest { getCachedServiceUpdates = getCachedServiceUpdates, ignoreDirection = false ).let { result -> - assertEquals(1, result.size) + assertEquals(2, result.size) assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { assertEquals(rds1.uuid, it.targetUUID) + assertEquals("Text 10", it.text) + } + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) } } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), - tripIds = listOf("tripId177777"), // out-of-sync (static!=real-time) + tripIds = listOf("tripId12"), getCachedServiceUpdates = getCachedServiceUpdates, - ignoreDirection = false + ignoreDirection = true ).let { result -> assertEquals(2, result.size) - assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertNotNull(result.singleOrNull { it.targetTripId == "tripId12" }) { assertEquals(rds1.uuid, it.targetUUID) + assertEquals("Text 12", it.text) } - assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { - assertEquals(rds1.uuid, it.targetUUID) + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } + } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds1), + targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), + tripIds = listOf("tripId177777"), // out-of-sync (static!=real-time) + getCachedServiceUpdates = getCachedServiceUpdates, + ignoreDirection = false + ).let { result -> + if (true) { + assertEquals(1, result.size) + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } + } else { // FIXME + assertEquals(3, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.uuid, it.targetUUID) + assertEquals("Text 10", it.text) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { + assertEquals(rds1.uuid, it.targetUUID) + assertEquals("Text 11", it.text) + } + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } } } gtfsRealTimeProvider.getCached( @@ -98,7 +139,11 @@ class GTFSRealTimeServiceAlertsProviderTest { getCachedServiceUpdates = getCachedServiceUpdates, ignoreDirection = true ).let { result -> - assertEquals(0, result.size) + assertEquals(1, result.size) + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds2), @@ -107,7 +152,11 @@ class GTFSRealTimeServiceAlertsProviderTest { getCachedServiceUpdates = getCachedServiceUpdates, ignoreDirection = false ).let { result -> - assertEquals(0, result.size) + assertEquals(1, result.size) + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } } } From 8f6f2778a99323091d104b934aa23cf70046e059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 14:23:39 -0400 Subject: [PATCH 05/15] wip --- .../android/commons/PreferenceUtils.java | 8 ++ .../provider/GTFSRealTimeProvider.java | 15 ++++ .../provider/gtfs/GTFSRDSProviderExt.kt | 41 ++++++---- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 2 + .../provider/gtfs/GtfsRealTimeStorage.kt | 16 ++++ .../GTFSRealTimeServiceAlertsProvider.kt | 67 +++++++++++----- .../status/GTFSRealTimeTripUpdatesProvider.kt | 4 +- .../data/RouteDirectionStopTestFixtures.kt | 21 ++++- .../GTFSRealTimeServiceAlertsProviderTest.kt | 79 ++++++++----------- 9 files changed, 166 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/PreferenceUtils.java b/src/main/java/org/mtransit/android/commons/PreferenceUtils.java index afd3a841..f61592f8 100644 --- a/src/main/java/org/mtransit/android/commons/PreferenceUtils.java +++ b/src/main/java/org/mtransit/android/commons/PreferenceUtils.java @@ -443,6 +443,14 @@ public static void savePrefLcl(@Nullable final Context context, @NonNull final S savePrefLclAsync(context, prefKey, newValue); } + @WorkerThread + public static void savePrefLclSync(@Nullable final Context context, @NonNull final String prefKey, @Nullable final Boolean newValue) { + if (context == null) { + return; + } + savePref(getPrefLcl(context), prefKey, newValue); + } + @MainThread public static void savePrefLclAsync(@NonNull Context context, @NonNull String prefKey, @Nullable Boolean newValue) { new MTAsyncTask() { diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java index fef406ef..0865cabd 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -415,6 +415,20 @@ public static String getAGENCY_TRIP_UPDATES_URL_CACHED(@NonNull Context context) return agencyTripUpdatesUrlCached; } + @Nullable + private static String targetAuthority = null; + + /** + * Override if multiple {@link OCTranspoProvider} implementations in same app. + */ + @NonNull + public static String getTARGET_AUTHORITY(@NonNull Context context) { + if (targetAuthority == null) { + targetAuthority = context.getResources().getString(R.string.gtfs_real_time_for_poi_authority); + } + return targetAuthority; + } + @Nullable private static Boolean ignoreDirection = null; @@ -916,6 +930,7 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context MTLog.d(this, "loadAgencyServiceUpdateDataFromWWW() > service update: %s.", serviceUpdate); } } + GTFSRealTimeServiceAlertsProvider.setServiceUpdatesTripIdsOutOfSync(this, serviceUpdates); return serviceUpdates; default: MTLog.w(this, "ERROR: HTTP URL-Connection Response Code %s (Message: %s)", response.code(), diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt index 02c3185e..9a43aaaa 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt @@ -12,7 +12,7 @@ import org.mtransit.android.commons.provider.poi.POIProviderContract fun Context.getRDS( authority: String, - routeId: Long, + routeId: Long? = null, directionId: Long? = null, ): List? = try { contentResolver.query( @@ -24,17 +24,20 @@ fun Context.getRDS( POIProviderContract.Filter.toJSON( POIProviderContract.Filter.getNewSqlSelectionFilter( buildString { - append( - SqlUtils.getWhereEquals( - GTFSProviderContract.RouteDirectionStopColumns.T_ROUTE_K_ID, - routeId + routeId?.let { + append( + SqlUtils.getWhereEquals( + GTFSProviderContract.RouteDirectionStopColumns.T_ROUTE_K_ID, + it + ) ) - ) + } directionId?.let { - append(SqlUtils.AND) + if (isNotEmpty()) append(SqlUtils.AND) append(SqlUtils.getWhereEquals(GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_K_ID, it)) } - }) + } + ) ).toString(), null, SqlUtils.getSortOrderAscending(GTFSProviderContract.RouteDirectionStopColumns.T_DIRECTION_STOPS_K_STOP_SEQUENCE) @@ -57,10 +60,12 @@ fun Context.getRDS( fun Context.getTripIds(authority: String, routeId: Long, directionId: Long? = null) = getTrips(authority, routeId, directionId)?.map { it.tripId } +@JvmOverloads fun Context.getTrips( authority: String, - routeId: Long, + routeId: Long? = null, directionId: Long? = null, + tripIds: List? = null, ): List? = try { contentResolver.query( Uri.withAppendedPath( @@ -69,16 +74,22 @@ fun Context.getTrips( ), GTFSProviderContract.PROJECTION_TRIP, buildString { - append( - SqlUtils.getWhereEquals( - GTFSProviderContract.TripColumns.T_TRIP_K_ROUTE_ID, - routeId + routeId?.let { + append( + SqlUtils.getWhereEquals( + GTFSProviderContract.TripColumns.T_TRIP_K_ROUTE_ID, + it + ) ) - ) + } directionId?.let { - append(SqlUtils.AND) + if (isNotEmpty()) append(SqlUtils.AND) append(SqlUtils.getWhereEquals(GTFSProviderContract.TripColumns.T_TRIP_K_DIRECTION_ID, it)) } + tripIds?.let { + if (isNotEmpty()) append(SqlUtils.AND) + append(SqlUtils.getWhereIn(GTFSProviderContract.TripColumns.T_TRIP_K_TRIP_ID, it)) + } }, null, GTFSRDSProvider.TRIP_SORT_ORDER, diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 79ea597d..3783995b 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -22,6 +22,7 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTypeTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getTARGET_AUTHORITY import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isIGNORE_DIRECTION import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isUSE_URL_HASH_SECRET_AND_DATE import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteId @@ -36,6 +37,7 @@ import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTU val Number.isValidDirection get() = this in 0..1 val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(requireContextCompat()) +val GTFSRealTimeProvider.targetAuthority get() = getTARGET_AUTHORITY(requireContextCompat()) val GTFSRealTimeProvider.timeZone get() = getAGENCY_TIME_ZONE(requireContextCompat()) private val GTFSRealTimeProvider.routeIdCleanupPattern get() = getRouteIdCleanupPattern(requireContextCompat()) diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt index 6ee3f087..52df793c 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt @@ -128,6 +128,22 @@ object GtfsRealTimeStorage { PreferenceUtils.savePrefLclSync(context, PREF_KEY_SERVICE_UPDATE_LANGUAGES, languages) } + /** + * Override if multiple {@link GTFSRealTimeDbHelper} implementations in same app. + */ + private const val PREF_KEY_SERVICE_UPDATE_TRIP_IDS_OUT_OF_SYNC = "pGTFSRealTimeServiceAlertsTripIdsOutOfSync" + + @JvmStatic + @WorkerThread + fun getServiceUpdateTripIdsOutOfSync(context: Context, default: Boolean) = + PreferenceUtils.getPrefLcl(context, PREF_KEY_SERVICE_UPDATE_TRIP_IDS_OUT_OF_SYNC, default) + + @JvmStatic + @WorkerThread + fun saveServiceUpdateTripIdsOutOfSync(context: Context, outOfSync: Boolean) { + PreferenceUtils.savePrefLclSync(context, PREF_KEY_SERVICE_UPDATE_TRIP_IDS_OUT_OF_SYNC, outOfSync) + } + // endregion } diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index c3f6229b..12532135 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -12,19 +12,21 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTypeTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID +import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optAgencyId import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionIdValid import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteType import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt import org.mtransit.android.commons.provider.gtfs.agencyTag -import org.mtransit.android.commons.provider.gtfs.getPrimaryTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTripIds +import org.mtransit.android.commons.provider.gtfs.getTrips import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseStopId import org.mtransit.android.commons.provider.gtfs.parseTripId +import org.mtransit.android.commons.provider.gtfs.targetAuthority import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelector object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { @@ -33,40 +35,54 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { override fun getLogTag() = LOG_TAG + private var _tripIdsOutOfSync: Boolean? = null + + private fun GTFSRealTimeProvider.getTripIdOutOfSync() = _tripIdsOutOfSync + ?: context?.let { GtfsRealTimeStorage.getServiceUpdateTripIdsOutOfSync(it, false) }.also { + _tripIdsOutOfSync = it + } + @JvmStatic fun GTFSRealTimeProvider.getCached(filter: ServiceUpdateProviderContract.Filter) = - filter.getTargetUUIDs(this, includeAgencyTag = true, includeRouteType = true, includeStopTags = true) + getCached( + filter = filter, + tripIdsOutOfSync = getTripIdOutOfSync(), + getTripIds = { authority, routeId, directionId -> + context?.getTripIds(authority, routeId, directionId) + }, + getCachedServiceUpdates = { targetUUIDs, tripIds -> + getCachedServiceUpdatesS(targetUUIDs, tripIds) + }, + ) + + fun GTFSRealTimeProvider.getCached( + filter: ServiceUpdateProviderContract.Filter, + tripIdsOutOfSync: Boolean?, + getTripIds: (authority: String, routeId: Long, directionId: Long?) -> List?, + getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List?, + ): List? { + val tripIdsOutOfSync = tripIdsOutOfSync == true + return filter.getTargetUUIDs(this, includeAgencyTag = !tripIdsOutOfSync, includeRouteType = true, includeStopTags = true) ?.let { targetUUIDs -> - val tripIds = filter.targetAuthority?.let { targetAuthority -> + val tripIds = if (tripIdsOutOfSync) null + else filter.targetAuthority?.let { targetAuthority -> filter.routeId?.let { routeId -> - context?.getTripIds(targetAuthority, routeId, filter.directionId) + getTripIds(targetAuthority, routeId, filter.directionId) } } targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // trip IDs not required for GTFS Alerts }?.let { (targetUUIDs, tripIds) -> - getCached(filter, targetUUIDs, tripIds, getCachedServiceUpdates = { targetUUIDs, tripIds -> - getCachedServiceUpdatesS(targetUUIDs, tripIds) - }) + getCached(targetUUIDs, tripIds, getCachedServiceUpdates) } + } - fun GTFSRealTimeProvider.getCached( - filter: ServiceUpdateProviderContract.Filter, + private fun getCached( targetUUIDs: Map, tripIds: List?, getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List?, - ignoreDirection: Boolean = this.ignoreDirection, ) = buildList { ( - // 1 - trip IDs preferred for all result filtered correctly - tripIds?.let { getCachedServiceUpdates(targetUUIDs.keys, it) }?.takeIf { it.isNotEmpty() } - // 2 - fallback to: ignore TRIP IDS (outdated?) and try using primary target UUID only - // - only works if Route & Direction! provided - // -> can NOT show service alerts for the wrong direction - ?: if (ignoreDirection) null - else filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = false, includeStopTags = true) - ?.let { (providerTargetUUID, _) -> - getCachedServiceUpdates(setOf(providerTargetUUID), null) - }?.takeIf { it.isNotEmpty() } + getCachedServiceUpdates(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } ) ?.let { addAll(it) @@ -122,4 +138,15 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { setTextHTML(enhanceHtmlDateTime(requireContextCompat(), serviceUpdate.textHTML)) } } + + @JvmStatic + fun GTFSRealTimeProvider.setServiceUpdatesTripIdsOutOfSync(serviceUpdates: List) { + val context = context ?: return + val rtTripId = serviceUpdates.firstOrNull { it.targetTripId != null }?.targetTripId + val tripIdsOutOfSync = rtTripId?.let { + context.getTrips(targetAuthority, tripIds = listOf(it))?.size == 0 // no trip ID matches == out-of-sync + } ?: false // no real-time trip ID == not out-of-sync + GtfsRealTimeStorage.saveServiceUpdateTripIdsOutOfSync(context, tripIdsOutOfSync) + _tripIdsOutOfSync = tripIdsOutOfSync + } } diff --git a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt index b9ec0d0a..18980c0f 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/GTFSRealTimeTripUpdatesProvider.kt @@ -14,7 +14,6 @@ import org.mtransit.android.commons.data.arrival import org.mtransit.android.commons.data.departure import org.mtransit.android.commons.data.toNoData import org.mtransit.android.commons.provider.GTFSRealTimeProvider -import org.mtransit.android.commons.provider.GTFSRealTimeProvider.isIGNORE_DIRECTION import org.mtransit.android.commons.provider.gtfs.GtfsRealTimeStorage import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optDirectionIdValid import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip @@ -24,6 +23,7 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toTripUpdates import org.mtransit.android.commons.provider.gtfs.getRDS import org.mtransit.android.commons.provider.gtfs.getRDSSchedule import org.mtransit.android.commons.provider.gtfs.getTripIds +import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseTripId @@ -107,8 +107,6 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { } } - val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(this.requireContextCompat()) - private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyData( context: Context, filter: Schedule.ScheduleStatusFilter, diff --git a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt index 2488cd94..d81665c3 100644 --- a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt +++ b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt @@ -42,11 +42,28 @@ fun makeRDS( false, ) -fun RouteDirectionStop.getGTFSRTTargetUUID(ignoreDirection: Boolean = false): String = +fun Route.getGTFSRTTargetUUID(): String = + GTFSRealTimeProvider.getAgencyRouteTagTargetUUID( + authority, + originalIdHash.toString() + ) + +fun RouteDirection.getGTFSRTTargetUUID(): String = + GTFSRealTimeProvider.getAgencyRouteDirectionTagTargetUUID( + authority, + route.originalIdHash.toString(), + direction.originalDirectionIdOrNull, + ) ?: GTFSRealTimeProvider.getAgencyRouteTagTargetUUID( + authority, + route.originalIdHash.toString() + ) + + +fun RouteDirectionStop.getGTFSRTTargetUUID(): String = GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID( authority, route.originalIdHash.toString(), - direction.originalDirectionIdOrNull?.takeIf { !ignoreDirection }, + direction.originalDirectionIdOrNull, stop.originalIdHashString ) ?: GTFSRealTimeProvider.getAgencyRouteStopTagTargetUUID( authority, diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index 3d580f6b..f437c0d7 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -12,6 +12,7 @@ import org.mtransit.android.commons.data.clone import org.mtransit.android.commons.data.getGTFSRTTargetUUID import org.mtransit.android.commons.data.makeRDS import org.mtransit.android.commons.data.makeServiceUpdate +import org.mtransit.android.commons.data.toRouteDirection import org.mtransit.android.commons.provider.GTFSRealTimeProvider import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionTagTargetUUID @@ -20,7 +21,6 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRoute import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTypeTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID -import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.getCached import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID import org.mtransit.commons.CommonsApp @@ -60,9 +60,11 @@ class GTFSRealTimeServiceAlertsProviderTest { val cachedServiceUpdates = buildList { add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId10", text = "Text 10")) add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId11", text = "Text 11")) - add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(ignoreDirection = true), targetTripId = "tripId12", text = "Text 12")) + add(makeServiceUpdate(targetUUID = rds1.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId12", text = "Text 12")) + add(makeServiceUpdate(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", text = "Text 13")) add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = null, text = "Text 00")) } + val staticTripIds = cachedServiceUpdates.mapNotNull { it.targetTripId }.toSet() + "tripId22" val getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> cachedServiceUpdates.filter { serviceUpdate -> targetUUIDs.contains(serviceUpdate.targetUUID) @@ -71,11 +73,11 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), - targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), - tripIds = listOf("tripId10"), + getTripIds = { _, _, _ -> listOf("tripId10") }, getCachedServiceUpdates = getCachedServiceUpdates, - ignoreDirection = false + tripIdsOutOfSync = !staticTripIds.contains("tripId10"), ).let { result -> + assertNotNull(result) assertEquals(2, result.size) assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { assertEquals(rds1.uuid, it.targetUUID) @@ -88,14 +90,14 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), - targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), - tripIds = listOf("tripId12"), + getTripIds = { _, _, _ -> listOf("tripId12") }, getCachedServiceUpdates = getCachedServiceUpdates, - ignoreDirection = true + tripIdsOutOfSync = !staticTripIds.contains("tripId12"), ).let { result -> + assertNotNull(result) assertEquals(2, result.size) assertNotNull(result.singleOrNull { it.targetTripId == "tripId12" }) { - assertEquals(rds1.uuid, it.targetUUID) + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) assertEquals("Text 12", it.text) } assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { @@ -105,53 +107,36 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), - targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), - tripIds = listOf("tripId177777"), // out-of-sync (static!=real-time) + getTripIds = { _, _, _ -> listOf("tripId177777") }, getCachedServiceUpdates = getCachedServiceUpdates, - ignoreDirection = false + tripIdsOutOfSync = !staticTripIds.contains("tripId177777"), ).let { result -> - if (true) { - assertEquals(1, result.size) - assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { - assertNull(it.targetTripId) - assertEquals("Text 00", it.text) - } - } else { // FIXME - assertEquals(3, result.size) - assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { - assertEquals(rds1.uuid, it.targetUUID) - assertEquals("Text 10", it.text) - } - assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { - assertEquals(rds1.uuid, it.targetUUID) - assertEquals("Text 11", it.text) - } - assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { - assertNull(it.targetTripId) - assertEquals("Text 00", it.text) - } + assertNotNull(result) + assertEquals(4, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.uuid, it.targetUUID) + assertEquals("Text 10", it.text) } - } - gtfsRealTimeProvider.getCached( - filter = ServiceUpdateProviderContract.Filter(rds1), - targetUUIDs = rds1.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), - tripIds = listOf("tripId177777"), // out-of-sync (static!=real-time) - getCachedServiceUpdates = getCachedServiceUpdates, - ignoreDirection = true - ).let { result -> - assertEquals(1, result.size) - assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { - assertNull(it.targetTripId) - assertEquals("Text 00", it.text) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { + assertEquals(rds1.uuid, it.targetUUID) + assertEquals("Text 11", it.text) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId12" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("Text 12", it.text) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId13" }) { + assertEquals(rds1.route.uuid, it.targetUUID) + assertEquals("Text 13", it.text) } } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds2), - targetUUIDs = rds2.getTargetUUIDs(gtfsRealTimeProvider, includeAgencyTag = true, includeRouteType = true, includeStopTags = true), - tripIds = listOf("tripId22"), + getTripIds = { _, _, _ -> listOf("tripId22") }, getCachedServiceUpdates = getCachedServiceUpdates, - ignoreDirection = false + tripIdsOutOfSync = !staticTripIds.contains("tripId22"), ).let { result -> + assertNotNull(result) assertEquals(1, result.size) assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { assertNull(it.targetTripId) From d2b0e2a977a6f72091da627ab630dab82536082d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 15:07:54 -0400 Subject: [PATCH 06/15] wip --- .../provider/GTFSRealTimeProvider.java | 4 +- .../provider/gtfs/GtfsRealTimeStorage.kt | 32 ++++ .../GTFSRealTimeServiceAlertsProvider.kt | 6 +- .../GTFSRealTimeVehiclePositionsProvider.kt | 73 ++++++--- .../VehicleLocationProviderContract.kt | 8 +- .../data/RouteDirectionStopTestFixtures.kt | 6 +- .../GTFSRealTimeProviderTestFixtures.kt | 12 ++ .../GTFSRealTimeServiceAlertsProviderTest.kt | 31 ++-- ...TFSRealTimeVehiclePositionsProviderTest.kt | 139 ++++++++++++++++++ .../model/VehicleLocationTestFixtures.kt | 40 +++++ 10 files changed, 300 insertions(+), 51 deletions(-) create mode 100644 src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt create mode 100644 src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt create mode 100644 src/test/java/org/mtransit/android/commons/provider/vehiclelocations/model/VehicleLocationTestFixtures.kt diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java index 0865cabd..a78970b5 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -419,7 +419,7 @@ public static String getAGENCY_TRIP_UPDATES_URL_CACHED(@NonNull Context context) private static String targetAuthority = null; /** - * Override if multiple {@link OCTranspoProvider} implementations in same app. + * Override if multiple {@link GTFSRealTimeProvider} implementations in same app. */ @NonNull public static String getTARGET_AUTHORITY(@NonNull Context context) { @@ -930,7 +930,7 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context MTLog.d(this, "loadAgencyServiceUpdateDataFromWWW() > service update: %s.", serviceUpdate); } } - GTFSRealTimeServiceAlertsProvider.setServiceUpdatesTripIdsOutOfSync(this, serviceUpdates); + GTFSRealTimeServiceAlertsProvider.setTripIdsOutOfSync(this, serviceUpdates); return serviceUpdates; default: MTLog.w(this, "ERROR: HTTP URL-Connection Response Code %s (Message: %s)", response.code(), diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt index 52df793c..63f6b8da 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsRealTimeStorage.kt @@ -40,6 +40,22 @@ object GtfsRealTimeStorage { PreferenceUtils.savePrefLclSync(context, PREF_KEY_TRIP_UPDATE_LAST_UPDATE_CODE, code) } + /** + * Override if multiple {@link GTFSRealTimeDbHelper} implementations in same app. + */ + private const val PREF_KEY_TRIP_UPDATE_TRIP_IDS_OUT_OF_SYNC = "pGTFSRealTimeTripUpdateTripIdsOutOfSync" + + @JvmStatic + @WorkerThread + fun getTripUpdateTripIdsOutOfSync(context: Context, default: Boolean) = + PreferenceUtils.getPrefLcl(context, PREF_KEY_TRIP_UPDATE_TRIP_IDS_OUT_OF_SYNC, default) + + @JvmStatic + @WorkerThread + fun saveTripUpdateTripIdsOutOfSync(context: Context, outOfSync: Boolean) { + PreferenceUtils.savePrefLclSync(context, PREF_KEY_TRIP_UPDATE_TRIP_IDS_OUT_OF_SYNC, outOfSync) + } + // end region // region Vehicle location @@ -76,6 +92,22 @@ object GtfsRealTimeStorage { PreferenceUtils.savePrefLclSync(context, PREF_KEY_VEHICLE_LOCATION_LAST_UPDATE_CODE, code) } + /** + * Override if multiple {@link GTFSRealTimeDbHelper} implementations in same app. + */ + private const val PREF_KEY_VEHICLE_LOCATION_TRIP_IDS_OUT_OF_SYNC = "pGTFSRealTimeVehicleLocationTripIdsOutOfSync" + + @JvmStatic + @WorkerThread + fun getVehicleLocationTripIdsOutOfSync(context: Context, default: Boolean) = + PreferenceUtils.getPrefLcl(context, PREF_KEY_VEHICLE_LOCATION_TRIP_IDS_OUT_OF_SYNC, default) + + @JvmStatic + @WorkerThread + fun saveVehicleLocationTripIdsOutOfSync(context: Context, outOfSync: Boolean) { + PreferenceUtils.savePrefLclSync(context, PREF_KEY_VEHICLE_LOCATION_TRIP_IDS_OUT_OF_SYNC, outOfSync) + } + // endregion // region Service alerts diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 12532135..3c1c1316 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -1,5 +1,6 @@ package org.mtransit.android.commons.provider.serviceupdate +import androidx.annotation.VisibleForTesting import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.data.ServiceUpdate import org.mtransit.android.commons.data.makeServiceUpdateNoneList @@ -55,7 +56,8 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { }, ) - fun GTFSRealTimeProvider.getCached( + @VisibleForTesting + internal fun GTFSRealTimeProvider.getCached( filter: ServiceUpdateProviderContract.Filter, tripIdsOutOfSync: Boolean?, getTripIds: (authority: String, routeId: Long, directionId: Long?) -> List?, @@ -140,7 +142,7 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { } @JvmStatic - fun GTFSRealTimeProvider.setServiceUpdatesTripIdsOutOfSync(serviceUpdates: List) { + fun GTFSRealTimeProvider.setTripIdsOutOfSync(serviceUpdates: List) { val context = context ?: return val rtTripId = serviceUpdates.firstOrNull { it.targetTripId != null }?.targetTripId val tripIdsOutOfSync = rtTripId?.let { diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index 3cc41ab5..a1f571ab 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -1,6 +1,7 @@ package org.mtransit.android.commons.provider.vehiclelocations import android.content.Context +import androidx.annotation.VisibleForTesting import org.mtransit.android.commons.Constants import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.SecurityUtils @@ -25,13 +26,14 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.sortVehicles import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toVehicles import org.mtransit.android.commons.provider.gtfs.agencyTag -import org.mtransit.android.commons.provider.gtfs.getPrimaryTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTripIds +import org.mtransit.android.commons.provider.gtfs.getTrips import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseTripId +import org.mtransit.android.commons.provider.gtfs.targetAuthority import org.mtransit.android.commons.provider.vehiclelocations.VehicleLocationProvider.Companion.getCachedVehicleLocationsS import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation import org.mtransit.android.commons.secsToInstant @@ -83,39 +85,58 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { this * 2L // fewer calls to Cached API $$ } else this + private var _tripIdsOutOfSync: Boolean? = null + + private fun GTFSRealTimeProvider.getTripIdOutOfSync() = _tripIdsOutOfSync + ?: context?.let { GtfsRealTimeStorage.getVehicleLocationTripIdsOutOfSync(it, false) }.also { + _tripIdsOutOfSync = it + } + private const val INCLUDE_AGENCY_TAG = true // some transit agencies only use the trip IDs to target #TransitWindsor @JvmStatic fun GTFSRealTimeProvider.getCached(filter: VehicleLocationProviderContract.Filter) = - filter.getTargetUUIDs(this, includeAgencyTag = INCLUDE_AGENCY_TAG) + getCached( + filter, + tripIdsOutOfSync = getTripIdOutOfSync(), + getTripIds = { authority, routeId, directionId -> + context?.getTripIds(authority, routeId, directionId) + }, + getCachedVehicleLocations = { targetUUIDs, tripIds -> + getCachedVehicleLocationsS(targetUUIDs, tripIds) + } + ) + + @VisibleForTesting + internal fun GTFSRealTimeProvider.getCached( + filter: VehicleLocationProviderContract.Filter, + tripIdsOutOfSync: Boolean?, + getTripIds: (authority: String, routeId: Long, directionId: Long?) -> List?, + getCachedVehicleLocations: (targetUUIDs: Collection, tripIds: List?) -> List?, + ): List? { + val tripIdsOutOfSync = tripIdsOutOfSync == true + @Suppress("SimplifyBooleanWithConstants") + return filter.getTargetUUIDs(this, includeAgencyTag = INCLUDE_AGENCY_TAG && !tripIdsOutOfSync) ?.let { targetUUIDs -> - val tripIds = filter.targetAuthority?.let { targetAuthority -> + val tripIds = if (tripIdsOutOfSync) null + else filter.targetAuthority?.let { targetAuthority -> filter.routeId?.let { routeId -> - context?.getTripIds(targetAuthority, routeId, filter.directionId) + getTripIds(targetAuthority, routeId, filter.directionId) } } targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // no trip IDS == fallback to primary target UUID only }?.let { (targetUUIDs, tripIds) -> - getCached( - filter = filter, - targetUUIDs = targetUUIDs, - tripIds = tripIds, - ) + getCached(targetUUIDs, tripIds, getCachedVehicleLocations) } + } - fun GTFSRealTimeProvider.getCached(filter: VehicleLocationProviderContract.Filter, targetUUIDs: Map, tripIds: List?) = buildList { + private fun getCached( + targetUUIDs: Map, + tripIds: List?, + getCachedVehicleLocations: (targetUUIDs: Collection, tripIds: List?) -> List?, + ) = buildList { ( - // 1 - trip IDs preferred for all result filtered correctly - tripIds?.let { - getCachedVehicleLocationsS(targetUUIDs.keys, tripIds = it) - }?.takeIf { it.isNotEmpty() } - // 2 - fallback to: ignore TRIP IDS (outdated?) and try using primary target UUID only - // - only works if Route & Direction! provided - // -> can NOT show vehicle in wrong direction - ?: if (ignoreDirection) null - else filter.getPrimaryTargetUUIDs(this@getCached, ignoreDirection = false)?.let { (providerTargetUUID, _) -> - getCachedVehicleLocationsS(setOf(providerTargetUUID), tripIds = null) - }?.takeIf { it.isNotEmpty() } + getCachedVehicleLocations(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } ) ?.let { addAll(it) @@ -246,6 +267,16 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { } } + private fun GTFSRealTimeProvider.setTripIdsOutOfSync(vehicleLocations: MutableList) { + val context = context ?: return + val rtTripId = vehicleLocations.firstOrNull { it.targetTripId != null }?.targetTripId + val tripIdsOutOfSync = rtTripId?.let { + context.getTrips(targetAuthority, tripIds = listOf(it))?.size == 0 // no trip ID matches == out-of-sync + } ?: false // no real-time trip ID == not out-of-sync + GtfsRealTimeStorage.saveVehicleLocationTripIdsOutOfSync(context, tripIdsOutOfSync) + _tripIdsOutOfSync = tripIdsOutOfSync + } + private fun GTFSRealTimeProvider.processVehiclePositions( newLastUpdateInMs: Long, gVehiclePosition: GVehiclePosition, diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt index 628676f8..28c9e99b 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProviderContract.kt @@ -90,7 +90,7 @@ interface VehicleLocationProviderContract : ProviderContract { } } - data class Filter @Discouraged("use from() instead") constructor( + data class Filter @Discouraged("use secondary constructor() instead") constructor( val authority: String, override val poi: POI? = null, // RouteDirectionStop or DefaultPOI override val route: Route? = null, @@ -107,15 +107,15 @@ interface VehicleLocationProviderContract : ProviderContract { private set @SuppressLint("DiscouragedApi") - constructor(poi: POI, tripIds: List? = null) : + constructor(poi: POI) : this(authority = poi.authority, poi = poi) @SuppressLint("DiscouragedApi") - constructor(route: Route, tripIds: List? = null) : + constructor(route: Route) : this(authority = route.authority, route = route) @SuppressLint("DiscouragedApi") - constructor(routeDirection: RouteDirection, tripIds: List? = null) : + constructor(routeDirection: RouteDirection) : this(authority = routeDirection.authority, routeDirection = routeDirection) @Suppress("unused") // main app only diff --git a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt index d81665c3..11fe73de 100644 --- a/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt +++ b/src/test/java/org/mtransit/android/commons/data/RouteDirectionStopTestFixtures.kt @@ -58,9 +58,9 @@ fun RouteDirection.getGTFSRTTargetUUID(): String = route.originalIdHash.toString() ) - -fun RouteDirectionStop.getGTFSRTTargetUUID(): String = - GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID( +fun RouteDirectionStop.getGTFSRTTargetUUID(includeStopTags: Boolean): String = + if (!includeStopTags) this.toRouteDirection().getGTFSRTTargetUUID() + else GTFSRealTimeProvider.getAgencyRouteDirectionStopTagTargetUUID( authority, route.originalIdHash.toString(), direction.originalDirectionIdOrNull, diff --git a/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt b/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt new file mode 100644 index 00000000..70c61150 --- /dev/null +++ b/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt @@ -0,0 +1,12 @@ +package org.mtransit.android.commons.provider + +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever +import org.mtransit.android.commons.data.RouteDirectionStop + +fun GTFSRealTimeProvider.setupProviderForRDS(rds: RouteDirectionStop) { + whenever { getRouteTag(eq(rds.route)) } doReturn rds.route.originalIdHash.toString() + whenever { getDirectionTag(eq(rds.direction)) } doReturn rds.direction.originalDirectionIdOrNull + whenever { getStopTag(eq(rds.stop)) } doReturn rds.stop.originalIdHashString +} diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index f437c0d7..0807e7ff 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -3,10 +3,7 @@ package org.mtransit.android.commons.provider.serviceupdate import com.google.transit.realtime.entitySelector import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn -import org.mockito.kotlin.eq import org.mockito.kotlin.mock -import org.mockito.kotlin.whenever -import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.ServiceUpdate import org.mtransit.android.commons.data.clone import org.mtransit.android.commons.data.getGTFSRTTargetUUID @@ -23,12 +20,14 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyStopT import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.getCached import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID +import org.mtransit.android.commons.provider.setupProviderForRDS import org.mtransit.commons.CommonsApp import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.fail class GTFSRealTimeServiceAlertsProviderTest { @@ -42,29 +41,29 @@ class GTFSRealTimeServiceAlertsProviderTest { } @Test - fun test_getCached() { + fun test_getCached() { // ignoreDirection handled during cache creation val rds1 = makeRDS( authority = "static_agency_id", routeId = 1L, originalDirectionId = 1, stopId = 10, ) - setupProviderForRDS(rds1) + gtfsRealTimeProvider.setupProviderForRDS(rds1) val rds2 = makeRDS( authority = "static_agency_id", routeId = 2L, originalDirectionId = 0, stopId = 20, ) - setupProviderForRDS(rds2) + gtfsRealTimeProvider.setupProviderForRDS(rds2) val cachedServiceUpdates = buildList { - add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId10", text = "Text 10")) - add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(), targetTripId = "tripId11", text = "Text 11")) + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(true), targetTripId = "tripId10", text = "Text 10")) + add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(true), targetTripId = "tripId11", text = "Text 11")) add(makeServiceUpdate(targetUUID = rds1.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId12", text = "Text 12")) add(makeServiceUpdate(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", text = "Text 13")) add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = null, text = "Text 00")) } - val staticTripIds = cachedServiceUpdates.mapNotNull { it.targetTripId }.toSet() + "tripId22" + val staticTripIds = (cachedServiceUpdates.mapNotNull { it.targetTripId } + "tripId22").toSet() val getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> cachedServiceUpdates.filter { serviceUpdate -> targetUUIDs.contains(serviceUpdate.targetUUID) @@ -73,8 +72,8 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), - getTripIds = { _, _, _ -> listOf("tripId10") }, getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> listOf("tripId10") }, tripIdsOutOfSync = !staticTripIds.contains("tripId10"), ).let { result -> assertNotNull(result) @@ -90,8 +89,8 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), - getTripIds = { _, _, _ -> listOf("tripId12") }, getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> listOf("tripId12") }, tripIdsOutOfSync = !staticTripIds.contains("tripId12"), ).let { result -> assertNotNull(result) @@ -107,8 +106,8 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), - getTripIds = { _, _, _ -> listOf("tripId177777") }, getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> fail("should not call since out of sync for tripId177777") }, tripIdsOutOfSync = !staticTripIds.contains("tripId177777"), ).let { result -> assertNotNull(result) @@ -132,8 +131,8 @@ class GTFSRealTimeServiceAlertsProviderTest { } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds2), - getTripIds = { _, _, _ -> listOf("tripId22") }, getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> listOf("tripId22") }, tripIdsOutOfSync = !staticTripIds.contains("tripId22"), ).let { result -> assertNotNull(result) @@ -145,12 +144,6 @@ class GTFSRealTimeServiceAlertsProviderTest { } } - private fun setupProviderForRDS(rds: RouteDirectionStop) { - whenever { gtfsRealTimeProvider.getRouteTag(eq(rds.route)) } doReturn rds.route.originalIdHash.toString() - whenever { gtfsRealTimeProvider.getDirectionTag(eq(rds.direction)) } doReturn rds.direction.originalDirectionIdOrNull - whenever { gtfsRealTimeProvider.getStopTag(eq(rds.stop)) } doReturn rds.stop.originalIdHashString - } - @Test fun test_parseProviderTargetUUID() { // EMPTY diff --git a/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt new file mode 100644 index 00000000..63c51984 --- /dev/null +++ b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt @@ -0,0 +1,139 @@ +package org.mtransit.android.commons.provider.vehiclelocations + +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mtransit.android.commons.data.getGTFSRTTargetUUID +import org.mtransit.android.commons.data.makeRDS +import org.mtransit.android.commons.data.toRouteDirection +import org.mtransit.android.commons.provider.GTFSRealTimeProvider +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID +import org.mtransit.android.commons.provider.setupProviderForRDS +import org.mtransit.android.commons.provider.vehiclelocations.GTFSRealTimeVehiclePositionsProvider.getCached +import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation +import org.mtransit.android.commons.provider.vehiclelocations.model.makeVehicleLocation +import org.mtransit.commons.CommonsApp +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.fail + +class GTFSRealTimeVehiclePositionsProviderTest { + + private val gtfsRealTimeProvider: GTFSRealTimeProvider = mock { + on { getAgencyTag(anyOrNull()) } doReturn "static_agency_id" + } + + @BeforeTest + fun setUp() { + CommonsApp.setup(false) + } + + @Test + fun test() { + val rds1 = makeRDS( + authority = "static_agency_id", + routeId = 1L, + originalDirectionId = 1, + stopId = 10, + ) + gtfsRealTimeProvider.setupProviderForRDS(rds1) + val rds2 = makeRDS( + authority = "static_agency_id", + routeId = 2L, + originalDirectionId = 0, + stopId = 20, + ) + gtfsRealTimeProvider.setupProviderForRDS(rds2) + val cachedVehicleLocation = buildList { + add(makeVehicleLocation(targetUUID = rds1.getGTFSRTTargetUUID(false), targetTripId = "tripId10", vehicleId = "vehicleId10")) + add(makeVehicleLocation(targetUUID = rds1.getGTFSRTTargetUUID(false), targetTripId = "tripId11", vehicleId = "vehicleId11")) + add(makeVehicleLocation(targetUUID = rds1.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId12", vehicleId = "vehicleId12")) + add(makeVehicleLocation(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", vehicleId = "vehicleId13")) + add(makeVehicleLocation(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = "tripId00", vehicleId = "vehicleId00")) + } + val staticTripIds = (cachedVehicleLocation.mapNotNull { it.targetTripId } + "tripId22").toSet() + val getCachedVehicleLocations: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> + cachedVehicleLocation.filter { vehicleLocation -> + targetUUIDs.contains(vehicleLocation.targetUUID) + && vehicleLocation.targetTripId?.let { tripIds?.contains(it) } != false // ignore if target tripID or local trip ID null + }.map { it.copy() } + } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds1), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> listOf("tripId10", "tripId11", "tripId12", "tripId13") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId10"), + ).let { result -> + assertNotNull(result) + assertEquals(4, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId10", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId11", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId12" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId12", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId13" }) { + assertEquals(rds1.route.uuid, it.targetUUID) + assertEquals("vehicleId13", it.vehicleId) + } + } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds1.toRouteDirection()), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> listOf("tripId10", "tripId11", "tripId12", "tripId13") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId10"), + ).let { result -> + assertNotNull(result) + assertEquals(4, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId10", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId11", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId12" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId12", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId13" }) { + assertEquals(rds1.route.uuid, it.targetUUID) + assertEquals("vehicleId13", it.vehicleId) + } + } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds1.toRouteDirection()), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> fail("should not call since out of sync for tripId177777") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId177777"), + ).let { result -> + assertNotNull(result) + assertEquals(4, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId10", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId11" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId11", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId12" }) { + assertEquals(rds1.toRouteDirection().uuid, it.targetUUID) + assertEquals("vehicleId12", it.vehicleId) + } + assertNotNull(result.singleOrNull { it.targetTripId == "tripId13" }) { + assertEquals(rds1.route.uuid, it.targetUUID) + assertEquals("vehicleId13", it.vehicleId) + } + } + } +} diff --git a/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/model/VehicleLocationTestFixtures.kt b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/model/VehicleLocationTestFixtures.kt new file mode 100644 index 00000000..06a9de20 --- /dev/null +++ b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/model/VehicleLocationTestFixtures.kt @@ -0,0 +1,40 @@ +package org.mtransit.android.commons.provider.vehiclelocations.model + +import org.mtransit.android.commons.TimeUtilsK +import org.mtransit.android.commons.toMillis +import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.seconds +import kotlin.time.Instant + +fun makeVehicleLocation( + id: Int? = null, + authority: String = "authority", + targetUUID: String = "uuid", + targetTripId: String? = null, + lastUpdate: Instant = TimeUtilsK.currentInstant(), + maxValidity: Duration = 1.hours, + // + vehicleId: String? = null, + vehicleLabel: String? = null, + reportTimestamp: Instant? = lastUpdate - 1.seconds, + latitude: Float = 1.0f, + longitude: Float = 2.0f, + bearingDegrees: Int? = 45, + speedMetersPerSecond: Int? = null, +) = VehicleLocation( + id = id, + authority = authority, + targetUUID = targetUUID, + targetTripId = targetTripId, + lastUpdateInMs = lastUpdate.toMillis(), + maxValidityInMs = maxValidity.inWholeMilliseconds, + // + vehicleId = vehicleId, + vehicleLabel = vehicleLabel, + reportTimestamp = reportTimestamp, + latitude = latitude, + longitude = longitude, + bearingDegrees = bearingDegrees, + speedMetersPerSecond = speedMetersPerSecond, +) From a8fc6776c5863ae90bf7e5882eda9f05a8b07ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 15:25:35 -0400 Subject: [PATCH 07/15] wip --- .../provider/GTFSRealTimeProvider.java | 8 +- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 12 +++ .../GTFSRealTimeServiceAlertsProvider.kt | 18 ++-- .../GTFSRealTimeVehiclePositionsProvider.kt | 19 ++-- .../GTFSRealTimeProviderTestFixtures.kt | 2 + .../GTFSRealTimeServiceAlertsProviderTest.kt | 4 +- ...TFSRealTimeVehiclePositionsProviderTest.kt | 92 ++++++++++++++++++- 7 files changed, 130 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java index a78970b5..1239d85e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -906,6 +906,7 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context final String sourceLabel = SourceUtils.getSourceLabel( // always use source from official API getAgencyServiceAlertsUrlString(context, "T") ); + final boolean ignoreDirection = isIGNORE_DIRECTION(context); try { GtfsRealtime.FeedMessage gFeedMessage = GtfsRealtime.FeedMessage.parseFrom(response.body().bytes()); List> alertsWithIdPair = GtfsRealtimeExt.toAlertsWithIdPair(gFeedMessage.getEntityList()); @@ -916,7 +917,7 @@ private List loadAgencyServiceUpdateDataFromWWW(@NonNull Context if (Constants.DEBUG) { MTLog.d(this, "loadAgencyServiceUpdateDataFromWWW() > GTFS - [%s] %s", feedEntityId, GtfsRealtimeExt.toStringExt(gAlert)); } - final Set alertsServiceUpdates = processAlerts(context, sourceLabel, feedEntityId, newLastUpdateInMs, gAlert); + final Set alertsServiceUpdates = processAlerts(context, sourceLabel, feedEntityId, newLastUpdateInMs, gAlert, ignoreDirection); if (alertsServiceUpdates != null && !alertsServiceUpdates.isEmpty()) { serviceUpdates.addAll(alertsServiceUpdates); } @@ -982,7 +983,8 @@ private HashSet processAlerts( @NonNull String sourceLabel, @Nullable String feedEntityId, long newLastUpdateInMs, - GtfsRealtime.Alert gAlert + GtfsRealtime.Alert gAlert, + boolean ignoreDirection ) { if (gAlert == null) return null; java.util.List gInformedEntityList = gAlert.getInformedEntityList(); @@ -1006,7 +1008,7 @@ private HashSet processAlerts( MTLog.w(this, "processAlerts() > Alert targets another agency: %s", gInformedEntity.getAgencyId()); continue; } - final String targetUUID = GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID(this, gInformedEntity); + final String targetUUID = GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID(this, gInformedEntity, ignoreDirection); if (targetUUID == null || targetUUID.isEmpty()) { continue; } diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 3783995b..2b475fd9 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -157,6 +157,18 @@ fun Route.getTargetUUIDs( put(getAgencyRouteTagTargetUUID(provider.agencyTag, getRouteTag(provider)), uuid) } +fun GTFSRealTimeProvider.setTripIdsOutOfSync( + getOneTripId: () -> String?, + saveTripIdsOutOfSync: (context: Context, tripIdsOutOfSync: Boolean) -> Unit, +) { + val context = context ?: return + val rtTripId = getOneTripId() + val tripIdsOutOfSync = rtTripId?.let { + context.getTrips(targetAuthority, tripIds = listOf(it))?.size == 0 // no trip ID matches == out-of-sync + } ?: false // no real-time trip ID == not out-of-sync + saveTripIdsOutOfSync(context, tripIdsOutOfSync) +} + fun GTFSRealTimeProvider.makeRequest(context: Context, urlCachedString: String = "", getUrlString: (token: String) -> String): Request? { if (urlCachedString.isNotBlank()) { MTLog.i(this, "Loading from cached API (length: %d) '***'...", urlCachedString.length) diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 3c1c1316..62fe69c2 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -27,6 +27,7 @@ import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseStopId import org.mtransit.android.commons.provider.gtfs.parseTripId +import org.mtransit.android.commons.provider.gtfs.setTripIdsOutOfSync import org.mtransit.android.commons.provider.gtfs.targetAuthority import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelector @@ -102,11 +103,10 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { fun GTFSRealTimeProvider.parseTargetTripId(gEntitySelector: GEntitySelector) = gEntitySelector.optTrip?.let { parseTripId(it) } - @JvmOverloads @JvmStatic fun GTFSRealTimeProvider.parseProviderTargetUUID( gEntitySelector: GEntitySelector, - ignoreDirection: Boolean = this.ignoreDirection, + ignoreDirection: Boolean, ): String? { parseRouteId(gEntitySelector)?.let { routeId -> gEntitySelector.optDirectionIdValid?.takeIf { !ignoreDirection }?.let { directionId -> @@ -143,12 +143,12 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.setTripIdsOutOfSync(serviceUpdates: List) { - val context = context ?: return - val rtTripId = serviceUpdates.firstOrNull { it.targetTripId != null }?.targetTripId - val tripIdsOutOfSync = rtTripId?.let { - context.getTrips(targetAuthority, tripIds = listOf(it))?.size == 0 // no trip ID matches == out-of-sync - } ?: false // no real-time trip ID == not out-of-sync - GtfsRealTimeStorage.saveServiceUpdateTripIdsOutOfSync(context, tripIdsOutOfSync) - _tripIdsOutOfSync = tripIdsOutOfSync + setTripIdsOutOfSync( + getOneTripId = { serviceUpdates.firstOrNull { it.targetTripId != null }?.targetTripId }, + saveTripIdsOutOfSync = { context, tripIdsOutOfSync -> + GtfsRealTimeStorage.saveServiceUpdateTripIdsOutOfSync(context, tripIdsOutOfSync) + _tripIdsOutOfSync = tripIdsOutOfSync + } + ) } } diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index a1f571ab..31937cbb 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -33,6 +33,7 @@ import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseTripId +import org.mtransit.android.commons.provider.gtfs.setTripIdsOutOfSync import org.mtransit.android.commons.provider.gtfs.targetAuthority import org.mtransit.android.commons.provider.vehiclelocations.VehicleLocationProvider.Companion.getCachedVehicleLocationsS import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation @@ -236,6 +237,7 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > - new ${vehicleLocation.toStringShort()}.") } } + setTripIdsOutOfSync(vehicleLocations) return vehicleLocations } @@ -268,13 +270,13 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { } private fun GTFSRealTimeProvider.setTripIdsOutOfSync(vehicleLocations: MutableList) { - val context = context ?: return - val rtTripId = vehicleLocations.firstOrNull { it.targetTripId != null }?.targetTripId - val tripIdsOutOfSync = rtTripId?.let { - context.getTrips(targetAuthority, tripIds = listOf(it))?.size == 0 // no trip ID matches == out-of-sync - } ?: false // no real-time trip ID == not out-of-sync - GtfsRealTimeStorage.saveVehicleLocationTripIdsOutOfSync(context, tripIdsOutOfSync) - _tripIdsOutOfSync = tripIdsOutOfSync + setTripIdsOutOfSync( + getOneTripId = { vehicleLocations.firstOrNull { it.targetTripId != null }?.targetTripId }, + saveTripIdsOutOfSync = { context, tripIdsOutOfSync -> + GtfsRealTimeStorage.saveVehicleLocationTripIdsOutOfSync(context, tripIdsOutOfSync) + _tripIdsOutOfSync = tripIdsOutOfSync + } + ) } private fun GTFSRealTimeProvider.processVehiclePositions( @@ -302,7 +304,8 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { ) } - private fun GTFSRealTimeProvider.parseProviderTargetUUID(gVehiclePosition: GVehiclePosition, ignoreDirection: Boolean): String? { + @VisibleForTesting + internal fun GTFSRealTimeProvider.parseProviderTargetUUID(gVehiclePosition: GVehiclePosition, ignoreDirection: Boolean): String? { val gTripDescriptor = gVehiclePosition.optTrip ?: return null if (gTripDescriptor.hasModifiedTrip()) { MTLog.d(LOG_TAG, "parseTargetUUID() > unhandled modified trip: ${gTripDescriptor.toStringExt()}") diff --git a/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt b/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt index 70c61150..16c61f70 100644 --- a/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt +++ b/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt @@ -10,3 +10,5 @@ fun GTFSRealTimeProvider.setupProviderForRDS(rds: RouteDirectionStop) { whenever { getDirectionTag(eq(rds.direction)) } doReturn rds.direction.originalDirectionIdOrNull whenever { getStopTag(eq(rds.stop)) } doReturn rds.stop.originalIdHashString } + +fun stringIdToHash(originalId: String) = originalId.hashCode().toString() diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index 0807e7ff..24c23190 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -21,6 +21,7 @@ import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTa import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.getCached import org.mtransit.android.commons.provider.serviceupdate.GTFSRealTimeServiceAlertsProvider.parseProviderTargetUUID import org.mtransit.android.commons.provider.setupProviderForRDS +import org.mtransit.android.commons.provider.stringIdToHash import org.mtransit.commons.CommonsApp import kotlin.test.BeforeTest import kotlin.test.Test @@ -249,7 +250,4 @@ class GTFSRealTimeServiceAlertsProviderTest { assertEquals(getAgencyRouteTypeTagTargetUUID("static_agency_id", 3), result) } } - - private fun stringIdToHash(originalId: String) = originalId.hashCode().toString() - } diff --git a/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt index 63c51984..875cebb0 100644 --- a/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt @@ -1,5 +1,7 @@ package org.mtransit.android.commons.provider.vehiclelocations +import com.google.transit.realtime.tripDescriptor +import com.google.transit.realtime.vehiclePosition import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -7,9 +9,13 @@ import org.mtransit.android.commons.data.getGTFSRTTargetUUID import org.mtransit.android.commons.data.makeRDS import org.mtransit.android.commons.data.toRouteDirection import org.mtransit.android.commons.provider.GTFSRealTimeProvider +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteDirectionTagTargetUUID +import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyRouteTagTargetUUID import org.mtransit.android.commons.provider.GTFSRealTimeProvider.getAgencyTagTargetUUID import org.mtransit.android.commons.provider.setupProviderForRDS +import org.mtransit.android.commons.provider.stringIdToHash import org.mtransit.android.commons.provider.vehiclelocations.GTFSRealTimeVehiclePositionsProvider.getCached +import org.mtransit.android.commons.provider.vehiclelocations.GTFSRealTimeVehiclePositionsProvider.parseProviderTargetUUID import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation import org.mtransit.android.commons.provider.vehiclelocations.model.makeVehicleLocation import org.mtransit.commons.CommonsApp @@ -31,7 +37,7 @@ class GTFSRealTimeVehiclePositionsProviderTest { } @Test - fun test() { + fun test_getCached() { val rds1 = makeRDS( authority = "static_agency_id", routeId = 1L, @@ -53,7 +59,7 @@ class GTFSRealTimeVehiclePositionsProviderTest { add(makeVehicleLocation(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", vehicleId = "vehicleId13")) add(makeVehicleLocation(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = "tripId00", vehicleId = "vehicleId00")) } - val staticTripIds = (cachedVehicleLocation.mapNotNull { it.targetTripId } + "tripId22").toSet() + val staticTripIds = (cachedVehicleLocation.mapNotNull { it.targetTripId } + "tripId20").toSet() val getCachedVehicleLocations: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> cachedVehicleLocation.filter { vehicleLocation -> targetUUIDs.contains(vehicleLocation.targetUUID) @@ -135,5 +141,87 @@ class GTFSRealTimeVehiclePositionsProviderTest { assertEquals("vehicleId13", it.vehicleId) } } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds2), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> listOf("tripId20") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId20"), + ).let { result -> + assertNotNull(result) + assertEquals(0, result.size) + } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds2.toRouteDirection()), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> listOf("tripId20") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId20"), + ).let { result -> + assertNotNull(result) + assertEquals(0, result.size) + } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds2.toRouteDirection()), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> fail("should not call since out of sync for tripId177777") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId277777"), + ).let { result -> + assertNotNull(result) + assertEquals(0, result.size) + } + } + + @Test + fun test_parseProviderTargetUUID() { + gtfsRealTimeProvider.parseProviderTargetUUID( + gVehiclePosition = vehiclePosition { + trip = tripDescriptor {} + }, + ignoreDirection = false, + ).let { result -> + assertEquals(getAgencyTagTargetUUID("static_agency_id"), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gVehiclePosition = vehiclePosition { + trip = tripDescriptor { + routeId = "route_id" + } + }, + ignoreDirection = false, + ).let { result -> + assertEquals(getAgencyRouteTagTargetUUID("static_agency_id", stringIdToHash("route_id")), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gVehiclePosition = vehiclePosition { + trip = tripDescriptor { + routeId = "route_id" + directionId = 0 + } + }, + ignoreDirection = false, + ).let { result -> + assertEquals(getAgencyRouteDirectionTagTargetUUID("static_agency_id", stringIdToHash("route_id"), 0), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gVehiclePosition = vehiclePosition { + trip = tripDescriptor { + routeId = "route_id" + directionId = 777777777 // INVALID! + } + }, + ignoreDirection = false, + ).let { result -> + assertEquals(getAgencyRouteTagTargetUUID("static_agency_id", stringIdToHash("route_id")), result) + } + gtfsRealTimeProvider.parseProviderTargetUUID( + gVehiclePosition = vehiclePosition { + trip = tripDescriptor { + routeId = "route_id" + directionId = 0 + } + }, + ignoreDirection = true, + ).let { result -> + assertEquals(getAgencyRouteTagTargetUUID("static_agency_id", stringIdToHash("route_id")), result) + } } } From f995c98447db52740642472d92546cb37d81582d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 15:50:27 -0400 Subject: [PATCH 08/15] wip --- .../GTFSRealTimeServiceAlertsProvider.kt | 19 +++++-- .../GTFSRealTimeVehiclePositionsProvider.kt | 1 + .../GTFSRealTimeServiceAlertsProviderTest.kt | 57 ++++++++++++++++++- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 62fe69c2..1778b073 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -20,6 +20,7 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteType import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt import org.mtransit.android.commons.provider.gtfs.agencyTag +import org.mtransit.android.commons.provider.gtfs.getRouteTypeTag import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTripIds import org.mtransit.android.commons.provider.gtfs.getTrips @@ -65,7 +66,7 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List?, ): List? { val tripIdsOutOfSync = tripIdsOutOfSync == true - return filter.getTargetUUIDs(this, includeAgencyTag = !tripIdsOutOfSync, includeRouteType = true, includeStopTags = true) + return filter.getTargetUUIDs(this, includeAgencyTag = true, includeRouteType = true, includeStopTags = true) ?.let { targetUUIDs -> val tripIds = if (tripIdsOutOfSync) null else filter.targetAuthority?.let { targetAuthority -> @@ -76,6 +77,18 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // trip IDs not required for GTFS Alerts }?.let { (targetUUIDs, tripIds) -> getCached(targetUUIDs, tripIds, getCachedServiceUpdates) + .let { cache -> + if (!tripIdsOutOfSync) return@let cache + if (cache.isEmpty()) return@let cache + val targetUUIDsToBroad = buildList { + add(getAgencyTagTargetUUID(agencyTag)) + filter.route?.let { getAgencyRouteTypeTagTargetUUID(agencyTag, getRouteTypeTag(it)) }?.let { add(it) } + } + return@let cache.filterNot { serviceUpdate -> + // remove service updates targeted to the entire agency or all route type for a specific trip ID + serviceUpdate.targetUUID in targetUUIDsToBroad && serviceUpdate.targetTripId != null + } + } } } @@ -84,9 +97,7 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { tripIds: List?, getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List?, ) = buildList { - ( - getCachedServiceUpdates(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } - ) + getCachedServiceUpdates(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } ?.let { addAll(it) } diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index 31937cbb..02a2e7c9 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -218,6 +218,7 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { if (Constants.DEBUG) { MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > GTFS vehicles[${gVehiclePositions.size}]: ") } + val ignoreDirection = ignoreDirection for (gVehiclePosition in gVehiclePositions.sortVehicles()) { if (Constants.DEBUG) { MTLog.d(LOG_TAG, "loadAgencyDataFromWWW() > GTFS - ${gVehiclePosition.toStringExt()}.") diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index 24c23190..43c61eca 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -57,12 +57,20 @@ class GTFSRealTimeServiceAlertsProviderTest { stopId = 20, ) gtfsRealTimeProvider.setupProviderForRDS(rds2) + val rds3 = makeRDS( + authority = "static_agency_id", + routeId = 3L, + originalDirectionId = 0, + stopId = 30, + ) + gtfsRealTimeProvider.setupProviderForRDS(rds3) val cachedServiceUpdates = buildList { add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(true), targetTripId = "tripId10", text = "Text 10")) add(makeServiceUpdate(targetUUID = rds1.getGTFSRTTargetUUID(true), targetTripId = "tripId11", text = "Text 11")) add(makeServiceUpdate(targetUUID = rds1.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId12", text = "Text 12")) add(makeServiceUpdate(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", text = "Text 13")) add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = null, text = "Text 00")) + add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = "tripId01", text = "Text 01")) } val staticTripIds = (cachedServiceUpdates.mapNotNull { it.targetTripId } + "tripId22").toSet() val getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> @@ -105,6 +113,23 @@ class GTFSRealTimeServiceAlertsProviderTest { assertEquals("Text 00", it.text) } } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds1), + getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> listOf("tripId01") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId01"), + ).let { result -> + assertNotNull(result) + assertEquals(2, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId01" }) { + assertEquals("static_agency_id", it.targetUUID) + assertEquals("Text 01", it.text) + } + assertNotNull(result.singleOrNull { it.targetTripId == null }) { + assertEquals("static_agency_id", it.targetUUID) + assertEquals("Text 00", it.text) + } + } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds1), getCachedServiceUpdates = getCachedServiceUpdates, @@ -112,7 +137,7 @@ class GTFSRealTimeServiceAlertsProviderTest { tripIdsOutOfSync = !staticTripIds.contains("tripId177777"), ).let { result -> assertNotNull(result) - assertEquals(4, result.size) + assertEquals(5, result.size) assertNotNull(result.singleOrNull { it.targetTripId == "tripId10" }) { assertEquals(rds1.uuid, it.targetUUID) assertEquals("Text 10", it.text) @@ -129,6 +154,10 @@ class GTFSRealTimeServiceAlertsProviderTest { assertEquals(rds1.route.uuid, it.targetUUID) assertEquals("Text 13", it.text) } + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } } gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds2), @@ -143,6 +172,32 @@ class GTFSRealTimeServiceAlertsProviderTest { assertEquals("Text 00", it.text) } } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds3), + getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> listOf("tripId33") }, + tripIdsOutOfSync = false, + ).let { result -> + assertNotNull(result) + assertEquals(1, result.size) + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } + } + gtfsRealTimeProvider.getCached( + filter = ServiceUpdateProviderContract.Filter(rds3), + getCachedServiceUpdates = getCachedServiceUpdates, + getTripIds = { _, _, _ -> listOf("tripId33") }, + tripIdsOutOfSync = true, + ).let { result -> + assertNotNull(result) + assertEquals(1, result.size) + assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { + assertNull(it.targetTripId) + assertEquals("Text 00", it.text) + } + } } @Test From ce98a9f90595285bbcc6b0d0db518a54b0898292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 15:55:49 -0400 Subject: [PATCH 09/15] wip --- ...TFSRealTimeVehiclePositionsProviderTest.kt | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt index 875cebb0..8d3c4ee7 100644 --- a/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProviderTest.kt @@ -23,6 +23,7 @@ import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull +import kotlin.test.assertNull import kotlin.test.fail class GTFSRealTimeVehiclePositionsProviderTest { @@ -52,14 +53,23 @@ class GTFSRealTimeVehiclePositionsProviderTest { stopId = 20, ) gtfsRealTimeProvider.setupProviderForRDS(rds2) + val rds3 = makeRDS( + authority = "static_agency_id", + routeId = 3L, + originalDirectionId = 0, + stopId = 30, + ) + gtfsRealTimeProvider.setupProviderForRDS(rds3) val cachedVehicleLocation = buildList { add(makeVehicleLocation(targetUUID = rds1.getGTFSRTTargetUUID(false), targetTripId = "tripId10", vehicleId = "vehicleId10")) add(makeVehicleLocation(targetUUID = rds1.getGTFSRTTargetUUID(false), targetTripId = "tripId11", vehicleId = "vehicleId11")) add(makeVehicleLocation(targetUUID = rds1.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId12", vehicleId = "vehicleId12")) add(makeVehicleLocation(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", vehicleId = "vehicleId13")) add(makeVehicleLocation(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = "tripId00", vehicleId = "vehicleId00")) + add(makeVehicleLocation(targetUUID = rds3.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId30", vehicleId = "vehicleId30")) + add(makeVehicleLocation(targetUUID = rds3.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = null, vehicleId = "vehicleId31")) } - val staticTripIds = (cachedVehicleLocation.mapNotNull { it.targetTripId } + "tripId20").toSet() + val staticTripIds = (cachedVehicleLocation.mapNotNull { it.targetTripId } + "tripId20" + "tripId31").toSet() val getCachedVehicleLocations: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> cachedVehicleLocation.filter { vehicleLocation -> targetUUIDs.contains(vehicleLocation.targetUUID) @@ -168,6 +178,23 @@ class GTFSRealTimeVehiclePositionsProviderTest { assertNotNull(result) assertEquals(0, result.size) } + gtfsRealTimeProvider.getCached( + filter = VehicleLocationProviderContract.Filter(rds3.toRouteDirection()), + getCachedVehicleLocations = getCachedVehicleLocations, + getTripIds = { _, _, _ -> listOf("tripId30", "tripId31") }, + tripIdsOutOfSync = !staticTripIds.contains("tripId30"), + ).let { result -> + assertNotNull(result) + assertEquals(2, result.size) + assertNotNull(result.singleOrNull { it.vehicleId == "vehicleId30" }) { + assertEquals(rds3.toRouteDirection().uuid, it.targetUUID) + assertEquals("tripId30", it.targetTripId) + } + assertNotNull(result.singleOrNull { it.vehicleId == "vehicleId31" }) { + assertEquals(rds3.toRouteDirection().uuid, it.targetUUID) + assertNull(it.targetTripId) + } + } } @Test From 8cff49674e5d3d7de9378b94385c79cac61079b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 16:07:19 -0400 Subject: [PATCH 10/15] cleanup --- .../GTFSRealTimeVehiclePositionsProvider.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt index 02a2e7c9..a22eec97 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/GTFSRealTimeVehiclePositionsProvider.kt @@ -28,13 +28,11 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toVehicles import org.mtransit.android.commons.provider.gtfs.agencyTag import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTripIds -import org.mtransit.android.commons.provider.gtfs.getTrips import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.makeRequest import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseTripId import org.mtransit.android.commons.provider.gtfs.setTripIdsOutOfSync -import org.mtransit.android.commons.provider.gtfs.targetAuthority import org.mtransit.android.commons.provider.vehiclelocations.VehicleLocationProvider.Companion.getCachedVehicleLocationsS import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation import org.mtransit.android.commons.secsToInstant @@ -136,9 +134,7 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { tripIds: List?, getCachedVehicleLocations: (targetUUIDs: Collection, tripIds: List?) -> List?, ) = buildList { - ( - getCachedVehicleLocations(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } - ) + getCachedVehicleLocations(targetUUIDs.keys, tripIds)?.takeIf { it.isNotEmpty() } ?.let { addAll(it) } @@ -273,7 +269,7 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { private fun GTFSRealTimeProvider.setTripIdsOutOfSync(vehicleLocations: MutableList) { setTripIdsOutOfSync( getOneTripId = { vehicleLocations.firstOrNull { it.targetTripId != null }?.targetTripId }, - saveTripIdsOutOfSync = { context, tripIdsOutOfSync -> + saveTripIdsOutOfSync = { context, tripIdsOutOfSync -> GtfsRealTimeStorage.saveVehicleLocationTripIdsOutOfSync(context, tripIdsOutOfSync) _tripIdsOutOfSync = tripIdsOutOfSync } From 1960a644bc1d34d9366499c999f569206916f54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 10 Apr 2026 16:13:08 -0400 Subject: [PATCH 11/15] more --- .../GTFSRealTimeServiceAlertsProviderTest.kt | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt index 43c61eca..91206e0c 100644 --- a/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt +++ b/src/test/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProviderTest.kt @@ -71,8 +71,10 @@ class GTFSRealTimeServiceAlertsProviderTest { add(makeServiceUpdate(targetUUID = rds1.route.getGTFSRTTargetUUID(), targetTripId = "tripId13", text = "Text 13")) add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = null, text = "Text 00")) add(makeServiceUpdate(targetUUID = getAgencyTagTargetUUID("static_agency_id"), targetTripId = "tripId01", text = "Text 01")) + add(makeServiceUpdate(targetUUID = rds3.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = "tripId30", text = "Text 30")) + add(makeServiceUpdate(targetUUID = rds3.toRouteDirection().getGTFSRTTargetUUID(), targetTripId = null, text = "Text 31")) } - val staticTripIds = (cachedServiceUpdates.mapNotNull { it.targetTripId } + "tripId22").toSet() + val staticTripIds = (cachedServiceUpdates.mapNotNull { it.targetTripId } + "tripId22" + "tripId31").toSet() val getCachedServiceUpdates: (targetUUIDs: Collection, tripIds: List?) -> List? = { targetUUIDs, tripIds -> cachedServiceUpdates.filter { serviceUpdate -> targetUUIDs.contains(serviceUpdate.targetUUID) @@ -175,11 +177,19 @@ class GTFSRealTimeServiceAlertsProviderTest { gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds3), getCachedServiceUpdates = getCachedServiceUpdates, - getTripIds = { _, _, _ -> listOf("tripId33") }, + getTripIds = { _, _, _ -> listOf("tripId30", "tripId31") }, tripIdsOutOfSync = false, ).let { result -> assertNotNull(result) - assertEquals(1, result.size) + assertEquals(3, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId30" }) { + assertEquals(rds3.toRouteDirection().uuid, it.targetUUID) + assertEquals("Text 30", it.text) + } + assertNotNull(result.singleOrNull { it.text == "Text 31" }) { + assertEquals(rds3.toRouteDirection().uuid, it.targetUUID) + assertNull(it.targetTripId) + } assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { assertNull(it.targetTripId) assertEquals("Text 00", it.text) @@ -188,11 +198,19 @@ class GTFSRealTimeServiceAlertsProviderTest { gtfsRealTimeProvider.getCached( filter = ServiceUpdateProviderContract.Filter(rds3), getCachedServiceUpdates = getCachedServiceUpdates, - getTripIds = { _, _, _ -> listOf("tripId33") }, + getTripIds = { _, _, _ -> listOf("tripId30", "tripId31") }, tripIdsOutOfSync = true, ).let { result -> assertNotNull(result) - assertEquals(1, result.size) + assertEquals(3, result.size) + assertNotNull(result.singleOrNull { it.targetTripId == "tripId30" }) { + assertEquals(rds3.toRouteDirection().uuid, it.targetUUID) + assertEquals("Text 30", it.text) + } + assertNotNull(result.singleOrNull { it.text == "Text 31" }) { + assertEquals(rds3.toRouteDirection().uuid, it.targetUUID) + assertNull(it.targetTripId) + } assertNotNull(result.singleOrNull { it.targetUUID == "static_agency_id" }) { assertNull(it.targetTripId) assertEquals("Text 00", it.text) From 549a55e9ba6749d32500b147dee9e6731de21e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Mon, 13 Apr 2026 13:41:13 -0400 Subject: [PATCH 12/15] Update src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 2b475fd9..503e5c26 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -164,7 +164,7 @@ fun GTFSRealTimeProvider.setTripIdsOutOfSync( val context = context ?: return val rtTripId = getOneTripId() val tripIdsOutOfSync = rtTripId?.let { - context.getTrips(targetAuthority, tripIds = listOf(it))?.size == 0 // no trip ID matches == out-of-sync + context.getTrips(targetAuthority, tripIds = listOf(it))?.isEmpty() == true // no trip ID matches == out-of-sync } ?: false // no real-time trip ID == not out-of-sync saveTripIdsOutOfSync(context, tripIdsOutOfSync) } From d655b26e5a1f15f64a02ade101672a614e34c05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Mon, 13 Apr 2026 14:03:13 -0400 Subject: [PATCH 13/15] PR comments --- .../android/commons/data/DefaultPOI.java | 3 ++- .../android/commons/data/Direction.java | 1 + .../mtransit/android/commons/data/Route.java | 4 +-- .../android/commons/data/RouteDirection.java | 1 + .../commons/data/RouteDirectionStop.java | 1 + .../mtransit/android/commons/data/Stop.java | 1 + .../provider/gtfs/GTFSRDSProviderExt.kt | 2 +- .../provider/gtfs/GTFSRealTimeProviderExt.kt | 3 ++- .../GTFSRealTimeServiceAlertsProvider.kt | 10 +++---- .../ServiceUpdateProviderContract.java | 27 ++++++++++++++----- 10 files changed, 34 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java b/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java index 0a796758..769737cd 100644 --- a/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java +++ b/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java @@ -67,7 +67,8 @@ public DefaultPOI(@NonNull String authority, int id, @DataSourceType int dataSou @Override public boolean equals(Object o) { - if (!(o instanceof DefaultPOI)) return false; + if (o == null) return false; + if (this.getClass() != o.getClass()) return false; final DefaultPOI that = (DefaultPOI) o; return id == that.id && authority.equals(that.authority) diff --git a/src/main/java/org/mtransit/android/commons/data/Direction.java b/src/main/java/org/mtransit/android/commons/data/Direction.java index aa1f5d79..f55f5d03 100644 --- a/src/main/java/org/mtransit/android/commons/data/Direction.java +++ b/src/main/java/org/mtransit/android/commons/data/Direction.java @@ -276,6 +276,7 @@ public long getRouteId() { @Override public boolean equals(Object o) { + if (o == null) return false; if (!(o instanceof Direction)) return false; final Direction direction = (Direction) o; return id == direction.id diff --git a/src/main/java/org/mtransit/android/commons/data/Route.java b/src/main/java/org/mtransit/android/commons/data/Route.java index 45ad469a..d1c978e5 100644 --- a/src/main/java/org/mtransit/android/commons/data/Route.java +++ b/src/main/java/org/mtransit/android/commons/data/Route.java @@ -107,6 +107,7 @@ public String getColor() { @Nullable private Integer colorInt; + @SuppressWarnings("unused") // main app only @ColorInt public int getColorInt() { if (this.colorInt == null) { @@ -117,6 +118,7 @@ public int getColorInt() { @Override public boolean equals(Object o) { + if (o == null) return false; if (!(o instanceof Route)) return false; final Route route = (Route) o; return id == route.id @@ -126,7 +128,6 @@ public boolean equals(Object o) { && color.equals(route.color) && Objects.equals(originalIdHash, route.originalIdHash) && Objects.equals(type, route.type) - && Objects.equals(colorInt, route.colorInt) ; } @@ -140,7 +141,6 @@ public int hashCode() { result = 31 * result + color.hashCode(); result = 31 * result + Objects.hashCode(originalIdHash); result = 31 * result + Objects.hashCode(type); - result = 31 * result + Objects.hashCode(colorInt); return result; } diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirection.java b/src/main/java/org/mtransit/android/commons/data/RouteDirection.java index e7f803c0..a260f1da 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirection.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirection.java @@ -61,6 +61,7 @@ public boolean equals(int routeId, int directionIdId) { @Override public boolean equals(Object o) { + if (o == null) return false; if (!(o instanceof RouteDirection)) return false; final RouteDirection that = (RouteDirection) o; return route.equals(that.route) diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index 9601460a..df5b1d85 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -157,6 +157,7 @@ public boolean equals(int routeId, int directionIdId, int stopId) { @Override public boolean equals(Object o) { + if (o == null) return false; if (!(o instanceof RouteDirectionStop)) return false; if (!super.equals(o)) return false; final RouteDirectionStop that = (RouteDirectionStop) o; diff --git a/src/main/java/org/mtransit/android/commons/data/Stop.java b/src/main/java/org/mtransit/android/commons/data/Stop.java index f19ac093..ba78561a 100644 --- a/src/main/java/org/mtransit/android/commons/data/Stop.java +++ b/src/main/java/org/mtransit/android/commons/data/Stop.java @@ -214,6 +214,7 @@ public boolean isSameOriginalId(@Nullable String cleanedOriginalIdHash) { @Override public boolean equals(Object o) { + if (o == null) return false; if (!(o instanceof Stop)) return false; final Stop stop = (Stop) o; return id == stop.id diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt index 9a43aaaa..c7f7cd65 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRDSProviderExt.kt @@ -88,7 +88,7 @@ fun Context.getTrips( } tripIds?.let { if (isNotEmpty()) append(SqlUtils.AND) - append(SqlUtils.getWhereIn(GTFSProviderContract.TripColumns.T_TRIP_K_TRIP_ID, it)) + append(SqlUtils.getWhereInString(GTFSProviderContract.TripColumns.T_TRIP_K_TRIP_ID, it)) } }, null, diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt index 2b475fd9..7e27107a 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSRealTimeProviderExt.kt @@ -35,7 +35,7 @@ import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelecto import com.google.transit.realtime.GtfsRealtime.TripDescriptor as GTripDescriptor import com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate as GTUStopTimeUpdate -val Number.isValidDirection get() = this in 0..1 +val Int.isValidDirection get() = this in 0..1 val GTFSRealTimeProvider.ignoreDirection get() = isIGNORE_DIRECTION(requireContextCompat()) val GTFSRealTimeProvider.targetAuthority get() = getTARGET_AUTHORITY(requireContextCompat()) val GTFSRealTimeProvider.timeZone get() = getAGENCY_TIME_ZONE(requireContextCompat()) @@ -74,6 +74,7 @@ fun RouteDirectionStop.getRouteTag(provider: GTFSRealTimeProvider) = this.route. fun RouteDirectionStop.getDirectionTag(provider: GTFSRealTimeProvider) = this.direction.getDirectionTag(provider) fun RouteDirectionStop.getStopTag(provider: GTFSRealTimeProvider) = this.stop.getStopTag(provider) +@Suppress("unused") fun GTFSRealTimeProviderFilter.getPrimaryTargetUUIDs( provider: GTFSRealTimeProvider, ignoreDirection: Boolean = false, diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 1778b073..53b73159 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -20,16 +20,12 @@ import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optRouteType import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.optTrip import org.mtransit.android.commons.provider.gtfs.GtfsRealtimeExt.toStringExt import org.mtransit.android.commons.provider.gtfs.agencyTag -import org.mtransit.android.commons.provider.gtfs.getRouteTypeTag import org.mtransit.android.commons.provider.gtfs.getTargetUUIDs import org.mtransit.android.commons.provider.gtfs.getTripIds -import org.mtransit.android.commons.provider.gtfs.getTrips -import org.mtransit.android.commons.provider.gtfs.ignoreDirection import org.mtransit.android.commons.provider.gtfs.parseRouteId import org.mtransit.android.commons.provider.gtfs.parseStopId import org.mtransit.android.commons.provider.gtfs.parseTripId import org.mtransit.android.commons.provider.gtfs.setTripIdsOutOfSync -import org.mtransit.android.commons.provider.gtfs.targetAuthority import com.google.transit.realtime.GtfsRealtime.EntitySelector as GEntitySelector object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { @@ -70,8 +66,8 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { ?.let { targetUUIDs -> val tripIds = if (tripIdsOutOfSync) null else filter.targetAuthority?.let { targetAuthority -> - filter.routeId?.let { routeId -> - getTripIds(targetAuthority, routeId, filter.directionId) + filter.targetRouteId?.let { routeId -> + getTripIds(targetAuthority, routeId, filter.targetDirectionId) } } targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // trip IDs not required for GTFS Alerts @@ -82,7 +78,7 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { if (cache.isEmpty()) return@let cache val targetUUIDsToBroad = buildList { add(getAgencyTagTargetUUID(agencyTag)) - filter.route?.let { getAgencyRouteTypeTagTargetUUID(agencyTag, getRouteTypeTag(it)) }?.let { add(it) } + filter.targetRoute?.let { getAgencyRouteTypeTagTargetUUID(agencyTag, getRouteTypeTag(it)) }?.let { add(it) } } return@let cache.filterNot { serviceUpdate -> // remove service updates targeted to the entire agency or all route type for a specific trip ID diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java index 24a8316d..ce2aff4c 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java @@ -12,6 +12,7 @@ import org.mtransit.android.commons.MTLog; import org.mtransit.android.commons.SecureStringUtils; import org.mtransit.android.commons.data.DefaultPOI; +import org.mtransit.android.commons.data.Direction; import org.mtransit.android.commons.data.POI; import org.mtransit.android.commons.data.Route; import org.mtransit.android.commons.data.RouteDirection; @@ -209,30 +210,42 @@ public String getTargetAuthority() { } @Nullable - public Long getRouteId() { + public Route getTargetRoute() { if (this.poi != null && this.poi instanceof RouteDirectionStop) { - return ((RouteDirectionStop) this.poi).getRoute().getId(); + return ((RouteDirectionStop) this.poi).getRoute(); } if (this.route != null) { - return this.route.getId(); + return this.route; } if (this.routeDirection != null) { - return this.routeDirection.getRoute().getId(); + return this.routeDirection.getRoute(); } return null; } @Nullable - public Long getDirectionId() { + public Long getTargetRouteId() { + final Route targetRoute = getTargetRoute(); + return targetRoute == null ? null : targetRoute.getId(); + } + + @Nullable + public Direction getTargetDirection() { if (this.poi != null && this.poi instanceof RouteDirectionStop) { - return ((RouteDirectionStop) this.poi).getDirection().getId(); + return ((RouteDirectionStop) this.poi).getDirection(); } if (this.routeDirection != null) { - return this.routeDirection.getDirection().getId(); + return this.routeDirection.getDirection(); } return null; } + @Nullable + public Long getTargetDirectionId() { + final Direction targetDirection = getTargetDirection(); + return targetDirection == null ? null : targetDirection.getId(); + } + @Nullable @Override public POI getPoi() { From 3f6a3dee5e1095995ba3bf7aeb0b2b19d10da423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Mon, 13 Apr 2026 14:18:12 -0400 Subject: [PATCH 14/15] PR comments --- .../android/commons/data/DefaultPOI.java | 26 +++++++++---------- .../android/commons/data/Direction.java | 14 +++++----- .../mtransit/android/commons/data/Route.java | 18 ++++++------- .../android/commons/data/RouteDirection.java | 9 ++++--- .../commons/data/RouteDirectionStop.java | 15 ++++++----- .../mtransit/android/commons/data/Stop.java | 18 ++++++------- .../GTFSRealTimeServiceAlertsProvider.kt | 4 +-- 7 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java b/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java index 769737cd..572b214f 100644 --- a/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java +++ b/src/main/java/org/mtransit/android/commons/data/DefaultPOI.java @@ -86,19 +86,19 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 0; - result = 31 * result + id; - result = 31 * result + authority.hashCode(); - result = 31 * result + name.hashCode(); - result = 31 * result + Double.hashCode(lat); - result = 31 * result + Double.hashCode(lng); - result = 31 * result + accessible; - result = 31 * result + type; - result = 31 * result + dataSourceTypeId; - result = 31 * result + statusType; - result = 31 * result + actionsType; - result = 31 * result + Objects.hashCode(scoreOpt); - return result; + return Objects.hash( + id, + authority, + name, + lat, + lng, + accessible, + type, + dataSourceTypeId, + statusType, + actionsType, + scoreOpt + ); } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/data/Direction.java b/src/main/java/org/mtransit/android/commons/data/Direction.java index f55f5d03..71967902 100644 --- a/src/main/java/org/mtransit/android/commons/data/Direction.java +++ b/src/main/java/org/mtransit/android/commons/data/Direction.java @@ -289,13 +289,13 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 0; - result = 31 * result + Long.hashCode(id); - result = 31 * result + authority.hashCode(); - result = 31 * result + headsignType; - result = 31 * result + headsignValue.hashCode(); - result = 31 * result + Long.hashCode(routeId); - return result; + return Objects.hash( + id, + authority, + headsignType, + headsignValue, + routeId + ); } public static class HeadSignComparator implements Comparator, MTLog.Loggable { diff --git a/src/main/java/org/mtransit/android/commons/data/Route.java b/src/main/java/org/mtransit/android/commons/data/Route.java index d1c978e5..fa1c659c 100644 --- a/src/main/java/org/mtransit/android/commons/data/Route.java +++ b/src/main/java/org/mtransit/android/commons/data/Route.java @@ -133,15 +133,15 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 0; - result = 31 * result + Long.hashCode(id); - result = 31 * result + authority.hashCode(); - result = 31 * result + shortName.hashCode(); - result = 31 * result + longName.hashCode(); - result = 31 * result + color.hashCode(); - result = 31 * result + Objects.hashCode(originalIdHash); - result = 31 * result + Objects.hashCode(type); - return result; + return Objects.hash( + id, + authority, + shortName, + longName, + color, + originalIdHash, + type + ); } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirection.java b/src/main/java/org/mtransit/android/commons/data/RouteDirection.java index a260f1da..5705d20e 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirection.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirection.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.Collection; +import java.util.Objects; public class RouteDirection implements Targetable, MTLog.Loggable { @@ -71,10 +72,10 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 0; - result = 31 * result + route.hashCode(); - result = 31 * result + direction.hashCode(); - return result; + return Objects.hash( + route, + direction + ); } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java index df5b1d85..f3c4d7e9 100644 --- a/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java +++ b/src/main/java/org/mtransit/android/commons/data/RouteDirectionStop.java @@ -171,13 +171,14 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 0; - result = 31 * result + route.hashCode(); - result = 31 * result + direction.hashCode(); - result = 31 * result + stop.hashCode(); - result = 31 * result + Boolean.hashCode(noPickup); - result = 31 * result + Objects.hashCode(alwaysLastTripStop); - return result; + return Objects.hash( + super.hashCode(), + route, + direction, + stop, + noPickup, + alwaysLastTripStop + ); } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/data/Stop.java b/src/main/java/org/mtransit/android/commons/data/Stop.java index ba78561a..b2236bdc 100644 --- a/src/main/java/org/mtransit/android/commons/data/Stop.java +++ b/src/main/java/org/mtransit/android/commons/data/Stop.java @@ -228,14 +228,14 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = 0; - result = 31 * result + id; - result = 31 * result + code.hashCode(); - result = 31 * result + name.hashCode(); - result = 31 * result + Double.hashCode(lat); - result = 31 * result + Double.hashCode(lng); - result = 31 * result + accessible; - result = 31 * result + Objects.hashCode(originalIdHash); - return result; + return Objects.hash( + id, + code, + name, + lat, + lng, + accessible, + originalIdHash + ); } } diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt index 53b73159..923fdf7a 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/GTFSRealTimeServiceAlertsProvider.kt @@ -66,8 +66,8 @@ object GTFSRealTimeServiceAlertsProvider : MTLog.Loggable { ?.let { targetUUIDs -> val tripIds = if (tripIdsOutOfSync) null else filter.targetAuthority?.let { targetAuthority -> - filter.targetRouteId?.let { routeId -> - getTripIds(targetAuthority, routeId, filter.targetDirectionId) + filter.targetRouteId?.let { targetRouteId -> + getTripIds(targetAuthority, targetRouteId, filter.targetDirectionId) } } targetUUIDs to tripIds?.takeIf { it.isNotEmpty() } // trip IDs not required for GTFS Alerts From d0dca836e5451c546b562f0f4179a6cbe3df1372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Mon, 13 Apr 2026 14:28:58 -0400 Subject: [PATCH 15/15] Update src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../commons/provider/GTFSRealTimeProviderTestFixtures.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt b/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt index 16c61f70..dc29f8c8 100644 --- a/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt +++ b/src/test/java/org/mtransit/android/commons/provider/GTFSRealTimeProviderTestFixtures.kt @@ -6,9 +6,9 @@ import org.mockito.kotlin.whenever import org.mtransit.android.commons.data.RouteDirectionStop fun GTFSRealTimeProvider.setupProviderForRDS(rds: RouteDirectionStop) { - whenever { getRouteTag(eq(rds.route)) } doReturn rds.route.originalIdHash.toString() - whenever { getDirectionTag(eq(rds.direction)) } doReturn rds.direction.originalDirectionIdOrNull - whenever { getStopTag(eq(rds.stop)) } doReturn rds.stop.originalIdHashString + whenever(getRouteTag(eq(rds.route))) doReturn rds.route.originalIdHash.toString() + whenever(getDirectionTag(eq(rds.direction))) doReturn rds.direction.originalDirectionIdOrNull + whenever(getStopTag(eq(rds.stop))) doReturn rds.stop.originalIdHashString } fun stringIdToHash(originalId: String) = originalId.hashCode().toString()