From f8fb32ffb3bfc200b464c28c0570688f429f432d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 25 Jun 2026 09:48:53 -0400 Subject: [PATCH 01/12] `ProviderContract.Filter` > shared filter params --- .../provider/common/ProviderContract.java | 141 ++++++++++ .../provider/common/ProviderContractExt.kt | 0 .../commons/provider/news/NewsProvider.java | 2 +- .../provider/news/NewsProviderContract.java | 252 +++++------------- .../provider/poi/POIProviderContract.java | 15 +- .../serviceupdate/ServiceUpdateProvider.java | 2 +- .../ServiceUpdateProviderContract.java | 137 +--------- .../provider/status/StatusProvider.java | 2 +- .../status/StatusProviderContract.java | 147 +--------- .../GTFSRealTimeVehiclePositionsProvider.kt | 2 +- .../NextBusVehicleLocationsProvider.kt | 2 +- .../VehicleLocationProvider.kt | 4 +- .../VehicleLocationProviderContract.kt | 42 +-- 13 files changed, 242 insertions(+), 506 deletions(-) create mode 100644 src/main/java/org/mtransit/android/commons/provider/common/ProviderContractExt.kt diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java index fda619e7..a0c20c04 100644 --- a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java @@ -5,10 +5,17 @@ import android.database.sqlite.SQLiteDatabase; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.json.JSONException; +import org.json.JSONObject; +import org.mtransit.android.commons.JSONUtils; import org.mtransit.android.commons.MTLog; +import org.mtransit.android.commons.SecureStringUtils; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.TimeUnit; public interface ProviderContract extends MTLog.Loggable { @@ -30,4 +37,138 @@ public interface ProviderContract extends MTLog.Loggable { @NonNull Context requireContextCompat(); + + abstract class Filter { + + private static final boolean CACHE_ONLY_DEFAULT = false; + private static final boolean IN_FOCUS_DEFAULT = false; + + @Nullable + private Boolean cacheOnly = null; + @Nullable + private Long cacheValidityInMs = null; + @Nullable + private Boolean inFocus = null; + @Nullable + private Map providedEncryptKeysMap = null; + + public boolean isCacheOnlyOrDefault() { + return this.cacheOnly == null ? CACHE_ONLY_DEFAULT : this.cacheOnly; + } + + @SuppressWarnings("unused") + @Nullable + public Boolean getCacheOnly() { + return this.cacheOnly; + } + + public void setCacheOnly(@Nullable Boolean cacheOnly) { + this.cacheOnly = cacheOnly; + } + + @Nullable + public Long getCacheValidityInMs() { + return this.cacheValidityInMs; + } + + @SuppressWarnings("unused") + public void setCacheValidityInMs(@Nullable Long cacheValidityInMs) { + this.cacheValidityInMs = cacheValidityInMs; + } + + public boolean isInFocusOrDefault() { + return this.inFocus == null ? IN_FOCUS_DEFAULT : this.inFocus; + } + + @SuppressWarnings("unused") + @Nullable + public Boolean getInFocus() { + return this.inFocus; + } + + public void setInFocus(@Nullable Boolean inFocus) { + this.inFocus = inFocus; + } + + public void setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { + this.providedEncryptKeysMap = providedEncryptKeysMap; + } + + public boolean hasProvidedEncryptKeysMap() { + return this.providedEncryptKeysMap != null && !this.providedEncryptKeysMap.isEmpty(); + } + + @Nullable + public Map getProvidedEncryptKeysMap() { + return this.providedEncryptKeysMap; + } + + @Nullable + public String getProvidedEncryptKey(@NonNull String key) { + if (this.providedEncryptKeysMap == null) return null; + final String value = this.providedEncryptKeysMap.get(key); + if (value == null || value.trim().isEmpty()) return null; + return value; + } + + public void appendProvidedKeys(@Nullable Map keysMap) { + final Map providedEncryptKeysMap = new HashMap<>(); + if (keysMap != null) { + for (Map.Entry entry : keysMap.entrySet()) { + providedEncryptKeysMap.put(entry.getKey(), SecureStringUtils.enc(entry.getValue())); + } + } + setProvidedEncryptKeysMap(providedEncryptKeysMap); + } + + private static final String JSON_CACHE_ONLY = "cacheOnly"; + private static final String JSON_CACHE_VALIDITY_IN_MS = "cacheValidityInMs"; + private static final String JSON_IN_FOCUS = "inFocus"; + private static final String JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap"; + + @Nullable + @SuppressWarnings("unused") + public static Long getCacheValidityInMsFromJSON(@NonNull JSONObject json) throws JSONException { + return json.has(JSON_CACHE_VALIDITY_IN_MS) ? json.getLong(JSON_CACHE_VALIDITY_IN_MS) : null; + } + + public static void toJSON(@NonNull Filter filter, @NonNull JSONObject json) throws JSONException { + if (filter.cacheOnly != null) { + json.put(JSON_CACHE_ONLY, filter.cacheOnly); + } + if (filter.cacheValidityInMs != null) { + json.put(JSON_CACHE_VALIDITY_IN_MS, filter.cacheValidityInMs); + } + if (filter.inFocus != null) { + json.put(JSON_IN_FOCUS, filter.inFocus); + } + if (filter.getProvidedEncryptKeysMap() != null) { + json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(filter.getProvidedEncryptKeysMap())); + } + } + + public static void fromJSON(@NonNull Filter filter, @NonNull JSONObject json) throws JSONException { + if (json.has(JSON_CACHE_ONLY)) { + filter.cacheOnly = json.getBoolean(JSON_CACHE_ONLY); + } + if (json.has(JSON_CACHE_VALIDITY_IN_MS)) { + filter.cacheValidityInMs = json.getLong(JSON_CACHE_VALIDITY_IN_MS); + } + if (json.has(JSON_IN_FOCUS)) { + filter.inFocus = json.getBoolean(JSON_IN_FOCUS); + } + if (json.has(JSON_PROVIDED_ENCRYPT_KEYS_MAP)) { + filter.providedEncryptKeysMap = JSONUtils.toMapOfStrings(json.getJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)); + } + } + + @NonNull + protected String toStringParts() { + final StringBuilder sb = new StringBuilder(); + if (this.cacheOnly != null) sb.append("cacheOnly:").append(this.cacheOnly).append(','); + if (this.cacheValidityInMs != null) sb.append("cacheValidityInMs:").append(MTLog.formatDuration(this.cacheValidityInMs)).append(','); + if (this.inFocus != null) sb.append("inFocus:").append(this.inFocus).append(","); + return sb.toString(); + } + } } diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContractExt.kt b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContractExt.kt new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java b/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java index bb2079fb..3e342f9e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java @@ -189,7 +189,7 @@ private static Cursor getNews(@NonNull NewsProviderContract provider, @Nullable return getNewsCursor(cachedNews); } long cacheValidityInMs = provider.getNewsValidityInMs(newsFilter.isInFocusOrDefault()); - final Long filterCacheValidityInMs = newsFilter.getCacheValidityInMsOrNull(); + final Long filterCacheValidityInMs = newsFilter.getCacheValidityInMs(); if (filterCacheValidityInMs != null && filterCacheValidityInMs > provider.getMinDurationBetweenNewsRefreshInMs(newsFilter.isInFocusOrDefault())) { cacheValidityInMs = filterCacheValidityInMs; } diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java index b46c1c66..13b817a6 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java @@ -135,7 +135,7 @@ interface Columns { }; @SuppressWarnings("WeakerAccess") - class Filter implements MTLog.Loggable { + class Filter extends ProviderContract.Filter implements MTLog.Loggable { private static final String LOG_TAG = NewsProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); @@ -145,24 +145,12 @@ public String getLogTag() { return LOG_TAG; } - private static final boolean CACHE_ONLY_DEFAULT = false; - - private static final boolean IN_FOCUS_DEFAULT = false; - - @Nullable - private List uuids; - @Nullable - private List targets; - @Nullable - private Boolean cacheOnly = null; @Nullable - private Long cacheValidityInMs = null; + private List articleUUIDs; // article UUIDs @Nullable - private Boolean inFocus = null; + private List targetUUIDs; // POI UUIDs @Nullable private Long minCreatedAtInMs = null; - @Nullable - private Map providedEncryptKeysMap = null; private Filter() { } @@ -173,37 +161,37 @@ public static Filter getNewEmptyFilter() { } @NonNull - public static Filter getNewUUIDFilter(@NonNull String uuid) { - return getNewUUIDsFilter(Collections.singletonList(uuid)); + public static Filter getNewArticleUUIDFilter(@NonNull String uuid) { + return getNewArticleUUIDsFilter(Collections.singletonList(uuid)); } @NonNull - public static Filter getNewUUIDsFilter(@Nullable List uuids) { - return new Filter().setUUIDs(uuids); + public static Filter getNewArticleUUIDsFilter(@Nullable List uuids) { + return new Filter().setArticleUUIDs(uuids); } @NonNull - private Filter setUUIDs(@Nullable List uuids) { - if (uuids == null || uuids.isEmpty()) { - throw new UnsupportedOperationException("Need at least 1 uuid!"); + private Filter setArticleUUIDs(@Nullable List articleUUIDs) { + if (articleUUIDs == null || articleUUIDs.isEmpty()) { + throw new UnsupportedOperationException("Need at least 1 article uuid!"); } - this.uuids = uuids; + this.articleUUIDs = articleUUIDs; return this; } @SuppressWarnings("unused") @Nullable - public List getUUIDs() { - return uuids; + public List getArticleUUIDs() { + return articleUUIDs; } @NonNull - public static Filter getNewTargetFilter(@NonNull POI poi) { - return getNewTargetsFilter(makeTargets(poi)); + public static Filter getNewTargetUUIDsFilter(@NonNull POI poi) { + return getNewTargetsUUIDsFilter(makeTargetUUIDs(poi)); } @NonNull - public static ArrayList makeTargets(@NonNull POI poi) { + public static ArrayList makeTargetUUIDs(@NonNull POI poi) { final ArrayList targets = new ArrayList<>(); targets.add(poi.getAuthority()); if (poi instanceof RouteDirectionStop) { @@ -214,28 +202,28 @@ public static ArrayList makeTargets(@NonNull POI poi) { @SuppressWarnings("unused") @NonNull - public static Filter getNewTargetFilter(@NonNull String targets) { - return getNewUUIDsFilter(Collections.singletonList(targets)); + public static Filter getNewTargetUUIDsFilter(@NonNull String targets) { + return getNewArticleUUIDsFilter(Collections.singletonList(targets)); } @NonNull - public static Filter getNewTargetsFilter(@Nullable List targets) { - return new Filter().setTargets(targets); + public static Filter getNewTargetsUUIDsFilter(@Nullable List targets) { + return new Filter().setTargetUUIDs(targets); } @NonNull - private Filter setTargets(List targets) { - if (targets == null || targets.isEmpty()) { + private Filter setTargetUUIDs(List targetUUIDs) { + if (targetUUIDs == null || targetUUIDs.isEmpty()) { throw new UnsupportedOperationException("Need at least 1 target!"); } - this.targets = targets; + this.targetUUIDs = targetUUIDs; return this; } @SuppressWarnings("unused") @Nullable - public List getTargets() { - return targets; + public List getTargetUUIDs() { + return targetUUIDs; } @NonNull @@ -254,13 +242,11 @@ public Long getMinCreatedAtInMsOrNull() { public String toString() { StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); if (isUUIDFilter(this)) { - sb.append("uuids:").append(this.uuids).append(','); + sb.append("articleUUIDs:").append(this.articleUUIDs).append(','); } else if (isTargetFilter(this)) { - sb.append("targets:").append(this.targets).append(','); + sb.append("targetsUUIDs:").append(this.targetUUIDs).append(','); } - sb.append("cacheOnly:").append(this.cacheOnly).append(','); - sb.append("inFocus:").append(this.inFocus).append(','); - sb.append("cacheValidityInMs:").append(this.cacheValidityInMs).append(','); + sb.append(super.toStringParts()); sb.append("minCreatedAtInMs:").append(this.minCreatedAtInMs); sb.append(']'); return sb.toString(); @@ -271,29 +257,29 @@ public String toString() { public String toStringTargetsAndUuid() { final StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); if (isUUIDFilter(this)) { - sb.append("uuids:").append(this.uuids).append(','); + sb.append("articleUUIDs:").append(this.articleUUIDs).append(','); } else if (isTargetFilter(this)) { - sb.append("targets:").append(this.targets).append(','); + sb.append("targetsUUIDs:").append(this.targetUUIDs).append(','); } sb.append(']'); return sb.toString(); } public static boolean isUUIDFilter(@Nullable Filter newsFilter) { - return newsFilter != null && CollectionUtils.getSize(newsFilter.uuids) > 0; + return newsFilter != null && CollectionUtils.getSize(newsFilter.articleUUIDs) > 0; } public static boolean isTargetFilter(@Nullable Filter newsFilter) { - return newsFilter != null && CollectionUtils.getSize(newsFilter.targets) > 0; + return newsFilter != null && CollectionUtils.getSize(newsFilter.targetUUIDs) > 0; } @NonNull public String getSqlSelection(@NonNull String uuidTableColumn, @NonNull String targetColumn, @NonNull String createdAtColumn) { StringBuilder sb = new StringBuilder(); if (isUUIDFilter(this)) { - sb.append(SqlUtils.getWhereInString(uuidTableColumn, this.uuids)); + sb.append(SqlUtils.getWhereInString(uuidTableColumn, this.articleUUIDs)); } else if (isTargetFilter(this)) { - sb.append(SqlUtils.getWhereInString(targetColumn, this.targets)); + sb.append(SqlUtils.getWhereInString(targetColumn, this.targetUUIDs)); } if (getMinCreatedAtInMsOrNull() != null) { if (sb.length() > 0) { @@ -304,92 +290,6 @@ public String getSqlSelection(@NonNull String uuidTableColumn, @NonNull String t return sb.toString(); } - @SuppressWarnings("unused") - @NonNull - public Filter setCacheOnly(@Nullable Boolean cacheOnly) { - this.cacheOnly = cacheOnly; - return this; - } - - public boolean isCacheOnlyOrDefault() { - return this.cacheOnly == null ? CACHE_ONLY_DEFAULT : this.cacheOnly; - } - - @Nullable - public Boolean getCacheOnlyOrNull() { - return this.cacheOnly; - } - - @NonNull - public Filter setInFocus(@Nullable Boolean inFocus) { - this.inFocus = inFocus; - return this; - } - - public boolean isInFocusOrDefault() { - return this.inFocus == null ? IN_FOCUS_DEFAULT : this.inFocus; - } - - @Nullable - public Boolean getInFocusOrNull() { - return this.inFocus; - } - - @Nullable - public Long getCacheValidityInMsOrNull() { - return this.cacheValidityInMs; - } - - @SuppressWarnings("unused") - public boolean hasCacheValidityInMs() { - return this.cacheValidityInMs != null && this.cacheValidityInMs > 0; - } - - @SuppressWarnings("unused") - @NonNull - public Filter setCacheValidityInMs(@Nullable Long cacheValidityInMs) { - this.cacheValidityInMs = cacheValidityInMs; - return this; - } - - @NonNull - public Filter setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { - this.providedEncryptKeysMap = providedEncryptKeysMap; - return this; - } - - public boolean hasProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap != null && !this.providedEncryptKeysMap.isEmpty(); - } - - @Nullable - public Map getProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap; - } - - @Nullable - public String getProvidedEncryptKey(@NonNull String key) { - if (this.providedEncryptKeysMap == null) { - return null; - } - final String value = this.providedEncryptKeysMap.get(key); - if (value == null || value.trim().isEmpty()) { - return null; - } - return value; - } - - @NonNull - public Filter appendProvidedKeys(@Nullable Map keysMap) { - final Map providedEncryptKeysMap = new HashMap<>(); - if (keysMap != null) { - for (Map.Entry entry : keysMap.entrySet()) { - providedEncryptKeysMap.put(entry.getKey(), SecureStringUtils.enc(entry.getValue())); - } - } - return setProvidedEncryptKeysMap(providedEncryptKeysMap); - } - @Nullable public static Filter fromJSONString(@Nullable String jsonString) { try { @@ -400,48 +300,33 @@ public static Filter fromJSONString(@Nullable String jsonString) { } } - private static final String JSON_UUIDS = "uuids"; - private static final String JSON_TARGETS = "targets"; - private static final String JSON_CACHE_ONLY = "cacheOnly"; - private static final String JSON_IN_FOCUS = "inFocus"; - private static final String JSON_CACHE_VALIDITY_IN_MS = "cacheValidityInMs"; + private static final String JSON_ARTICLE_UUIDS = "uuids"; // article UUIDs + private static final String JSON_TARGETS_UUIDS = "targets"; // POI UUIDs private static final String JSON_MIN_CREATED_AT_IN_MS = "minCreatedAtInMs"; - private static final String JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap"; @Nullable public static Filter fromJSON(@NonNull JSONObject json) { try { - Filter newsFilter = new Filter(); - JSONArray jUUIDs = json.optJSONArray(JSON_UUIDS); - JSONArray jTargets = json.optJSONArray(JSON_TARGETS); - if (jUUIDs != null && jUUIDs.length() > 0) { - ArrayList uuids = new ArrayList<>(); - for (int i = 0; i < jUUIDs.length(); i++) { - uuids.add(jUUIDs.getString(i)); + final Filter newsFilter = new Filter(); + ProviderContract.Filter.fromJSON(newsFilter, json); + final JSONArray jArticleUUIDs = json.optJSONArray(JSON_ARTICLE_UUIDS); + final JSONArray jTargetsUUIDs = json.optJSONArray(JSON_TARGETS_UUIDS); + if (jArticleUUIDs != null && jArticleUUIDs.length() > 0) { + final ArrayList articleUUIDs = new ArrayList<>(); + for (int i = 0; i < jArticleUUIDs.length(); i++) { + articleUUIDs.add(jArticleUUIDs.getString(i)); } - newsFilter.setUUIDs(uuids); - } else if (jTargets != null && jTargets.length() > 0) { - ArrayList targets = new ArrayList<>(); - for (int i = 0; i < jTargets.length(); i++) { - targets.add(jTargets.getString(i)); + newsFilter.setArticleUUIDs(articleUUIDs); + } else if (jTargetsUUIDs != null && jTargetsUUIDs.length() > 0) { + final ArrayList targetsUUIDs = new ArrayList<>(); + for (int i = 0; i < jTargetsUUIDs.length(); i++) { + targetsUUIDs.add(jTargetsUUIDs.getString(i)); } - newsFilter.setTargets(targets); - } - if (json.has(JSON_CACHE_ONLY)) { - newsFilter.cacheOnly = json.getBoolean(JSON_CACHE_ONLY); - } - if (json.has(JSON_IN_FOCUS)) { - newsFilter.inFocus = json.getBoolean(JSON_IN_FOCUS); - } - if (json.has(JSON_CACHE_VALIDITY_IN_MS)) { - newsFilter.cacheValidityInMs = json.getLong(JSON_CACHE_VALIDITY_IN_MS); + newsFilter.setTargetUUIDs(targetsUUIDs); } if (json.has(JSON_MIN_CREATED_AT_IN_MS)) { newsFilter.minCreatedAtInMs = json.getLong(JSON_MIN_CREATED_AT_IN_MS); } - if (json.has(JSON_PROVIDED_ENCRYPT_KEYS_MAP)) { - newsFilter.providedEncryptKeysMap = JSONUtils.toMapOfStrings(json.getJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)); - } return newsFilter; } catch (JSONException jsone) { MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); @@ -456,41 +341,30 @@ public String toJSONString() { @Nullable public static String toJSONString(@NonNull Filter newsFilter) { - JSONObject json = toJSON(newsFilter); + final JSONObject json = toJSON(newsFilter); return json == null ? null : json.toString(); } @Nullable public static JSONObject toJSON(@NonNull Filter newsFilter) { try { - JSONObject json = new JSONObject(); + final JSONObject json = new JSONObject(); + ProviderContract.Filter.toJSON(newsFilter, json); if (newsFilter.getMinCreatedAtInMsOrNull() != null) { json.put(JSON_MIN_CREATED_AT_IN_MS, newsFilter.getMinCreatedAtInMsOrNull()); } - if (newsFilter.getCacheOnlyOrNull() != null) { - json.put(JSON_CACHE_ONLY, newsFilter.getCacheOnlyOrNull()); - } - if (newsFilter.getInFocusOrNull() != null) { - json.put(JSON_IN_FOCUS, newsFilter.getInFocusOrNull()); - } - if (newsFilter.getCacheValidityInMsOrNull() != null) { - json.put(JSON_CACHE_VALIDITY_IN_MS, newsFilter.getCacheValidityInMsOrNull()); - } - if (isUUIDFilter(newsFilter) && newsFilter.uuids != null) { - JSONArray jUUIDs = new JSONArray(); - for (String uuid : newsFilter.uuids) { - jUUIDs.put(uuid); + if (isUUIDFilter(newsFilter) && newsFilter.articleUUIDs != null) { + final JSONArray jArticleUUIDs = new JSONArray(); + for (String articleUUID : newsFilter.articleUUIDs) { + jArticleUUIDs.put(articleUUID); } - json.put(JSON_UUIDS, jUUIDs); - } else if (isTargetFilter(newsFilter) && newsFilter.targets != null) { - JSONArray jTargets = new JSONArray(); - for (String uuid : newsFilter.targets) { - jTargets.put(uuid); + json.put(JSON_ARTICLE_UUIDS, jArticleUUIDs); + } else if (isTargetFilter(newsFilter) && newsFilter.targetUUIDs != null) { + final JSONArray jTargetUUIDs = new JSONArray(); + for (String targetUUID : newsFilter.targetUUIDs) { + jTargetUUIDs.put(targetUUID); } - json.put(JSON_TARGETS, jTargets); - } - if (newsFilter.getProvidedEncryptKeysMap() != null) { - json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(newsFilter.getProvidedEncryptKeysMap())); + json.put(JSON_TARGETS_UUIDS, jTargetUUIDs); } return json; } catch (JSONException jsone) { diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java index 208580f5..6d6f4179 100644 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java @@ -106,7 +106,7 @@ public static String getFkColumnName(@NonNull String key) { } @SuppressWarnings({"WeakerAccess", "unused"}) - class Filter implements MTLog.Loggable { + class Filter extends ProviderContract.Filter implements MTLog.Loggable { private static final String LOG_TAG = POIProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); @@ -262,10 +262,10 @@ public String toString() { sb.append("maxLat:").append(this.maxLat).append(','); sb.append("minLng:").append(this.minLng).append(','); sb.append("maxLng:").append(this.maxLng).append(','); - sb.append("optLoadedMinLat:").append(this.optLoadedMinLat).append(','); - sb.append("optLoadedMaxLat:").append(this.optLoadedMaxLat).append(','); - sb.append("optLoadedMinLng").append(this.optLoadedMinLng).append(','); - sb.append("optLoadedMaxLng:").append(this.optLoadedMaxLng).append(','); + if (optLoadedMinLat != null) sb.append("optLoadedMinLat:").append(this.optLoadedMinLat).append(','); + if (optLoadedMaxLat != null) sb.append("optLoadedMaxLat:").append(this.optLoadedMaxLat).append(','); + if (optLoadedMinLng != null) sb.append("optLoadedMinLng:").append(this.optLoadedMinLng).append(','); + if (optLoadedMaxLng != null) sb.append("optLoadedMaxLng:").append(this.optLoadedMaxLng).append(','); } else if (isUUIDFilter(this)) { sb.append("uuids:").append(this.uuids).append(','); } else if (isSearchKeywords(this)) { @@ -274,6 +274,7 @@ public String toString() { sb.append("sqlSelection:").append(this.sqlSelection).append(','); } sb.append("extras:").append(this.extras).append(','); + sb.append(super.toStringParts()); sb.append(']'); return sb.toString(); } @@ -506,7 +507,8 @@ public static Filter fromJSONString(@Nullable String jsonString) { @SuppressWarnings("ConstantConditions") private static Filter fromJSON(JSONObject json) { try { - Filter poiFilter = new Filter(); + final Filter poiFilter = new Filter(); + ProviderContract.Filter.fromJSON(poiFilter, json); Double lat; Double lng; Double aroundDiff; @@ -647,6 +649,7 @@ public static JSONObject toJSON(@Nullable Filter poiFilter) { } JSONArray jExtras = new JSONArray(); if (poiFilter != null) { + ProviderContract.Filter.toJSON(poiFilter, json); for (int i = 0; i < poiFilter.extras.size(); i++) { JSONObject jExtra = new JSONObject(); jExtra.put(JSON_EXTRAS_KEY, poiFilter.extras.keyAt(i)); diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java index 5f69cd62..d7eb9c9b 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java @@ -120,7 +120,7 @@ private static Cursor getServiceUpdates(ServiceUpdateProviderContract provider, return getServiceUpdateCursor(cachedServiceUpdates); } long cacheValidityInMs = provider.getServiceUpdateValidityInMs(serviceUpdateFilter.isInFocusOrDefault()); - Long filterCacheValidityInMs = serviceUpdateFilter.getCacheValidityInMsOrNull(); + Long filterCacheValidityInMs = serviceUpdateFilter.getCacheValidityInMs(); if (filterCacheValidityInMs != null && filterCacheValidityInMs > provider.getMinDurationBetweenServiceUpdateRefreshInMs(serviceUpdateFilter.isInFocusOrDefault())) { cacheValidityInMs = filterCacheValidityInMs; 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 27e6e7f5..b1da8789 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 @@ -10,7 +10,6 @@ import org.json.JSONObject; import org.mtransit.android.commons.JSONUtils; 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; @@ -22,9 +21,6 @@ import org.mtransit.android.commons.provider.common.ProviderContract; import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter; -import java.util.HashMap; -import java.util.Map; - public interface ServiceUpdateProviderContract extends ProviderContract { String SERVICE_UPDATE_PATH = "service"; @@ -97,7 +93,7 @@ class Columns { } @SuppressWarnings("WeakerAccess") - class Filter implements GTFSRealTimeProviderFilter, MTLog.Loggable { + class Filter extends ProviderContract.Filter implements GTFSRealTimeProviderFilter, MTLog.Loggable { private static final String LOG_TAG = ServiceUpdateProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); @@ -107,10 +103,6 @@ public String getLogTag() { return LOG_TAG; } - private static final boolean CACHE_ONLY_DEFAULT = false; - - private static final boolean IN_FOCUS_DEFAULT = false; - @Nullable private final POI poi; // RouteDirectionStop or DefaultPOI @Nullable @@ -120,15 +112,6 @@ public String getLogTag() { @Nullable private final RouteDirection routeDirection; - @Nullable - private Boolean cacheOnly = null; - @Nullable - private Long cacheValidityInMs = null; - @Nullable - private Boolean inFocus = null; - @Nullable - private Map providedEncryptKeysMap = null; - public Filter(@NonNull POI poi) { this.poi = poi; this.authority = poi.getAuthority(); @@ -153,11 +136,8 @@ public Filter(@NonNull String authority, @NonNull RouteDirection routeDirection) @NonNull @Override public String toString() { - final StringBuilder sb = new StringBuilder(); - sb.append(Filter.class.getSimpleName()) - .append("cacheOnly:").append(this.cacheOnly).append(',') - .append("inFocus:").append(this.inFocus).append(',') - .append("cacheValidityInMs:").append(this.cacheValidityInMs).append(','); + final StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()); + sb.append(super.toStringParts()); if (this.poi != null) { sb.append("poi:").append(this.poi).append(','); } @@ -263,87 +243,6 @@ public RouteDirection getRouteDirection() { return routeDirection; } - @SuppressWarnings("unused") - public void setCacheOnly(@Nullable Boolean cacheOnly) { - this.cacheOnly = cacheOnly; - } - - public boolean isCacheOnlyOrDefault() { - return this.cacheOnly == null ? CACHE_ONLY_DEFAULT : this.cacheOnly; - } - - @Nullable - public Boolean getCacheOnlyOrNull() { - return this.cacheOnly; - } - - public void setInFocus(@Nullable Boolean inFocus) { - this.inFocus = inFocus; - } - - public boolean isInFocusOrDefault() { - return this.inFocus == null ? IN_FOCUS_DEFAULT : this.inFocus; - } - - @Nullable - public Boolean getInFocusOrNull() { - return this.inFocus; - } - - @Nullable - public Long getCacheValidityInMsOrNull() { - return this.cacheValidityInMs; - } - - @SuppressWarnings("unused") - public boolean hasCacheValidityInMs() { - return this.cacheValidityInMs != null && this.cacheValidityInMs > 0; - } - - @SuppressWarnings("unused") - public void setCacheValidityInMs(@Nullable Long cacheValidityInMs) { - this.cacheValidityInMs = cacheValidityInMs; - } - - @NonNull - public Filter setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { - this.providedEncryptKeysMap = providedEncryptKeysMap; - return this; - } - - @SuppressWarnings("unused") - public boolean hasProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap != null && !this.providedEncryptKeysMap.isEmpty(); - } - - @Nullable - public Map getProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap; - } - - @Nullable - public String getProvidedEncryptKey(@NonNull String key) { - if (this.providedEncryptKeysMap == null) { - return null; - } - final String value = this.providedEncryptKeysMap.get(key); - if (value == null || value.trim().isEmpty()) { - return null; - } - return value; - } - - @NonNull - public Filter appendProvidedKeys(@Nullable Map keysMap) { - final Map providedEncryptKeysMap = new HashMap<>(); - if (keysMap != null) { - for (Map.Entry entry : keysMap.entrySet()) { - providedEncryptKeysMap.put(entry.getKey(), SecureStringUtils.enc(entry.getValue())); - } - } - return setProvidedEncryptKeysMap(providedEncryptKeysMap); - } - @Nullable public static Filter fromJSONString(@Nullable String jsonString) { try { @@ -358,10 +257,6 @@ public static Filter fromJSONString(@Nullable String jsonString) { private static final String JSON_ROUTE = "route"; private static final String JSON_ROUTE_DIRECTION = "routeDirection"; private static final String JSON_AUTHORITY = "authority"; - private static final String JSON_CACHE_ONLY = "cacheOnly"; - private static final String JSON_IN_FOCUS = "inFocus"; - private static final String JSON_CACHE_VALIDITY_IN_MS = "cacheValidityInMs"; - private static final String JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap"; @Nullable public static Filter fromJSON(@NonNull JSONObject json) { @@ -382,14 +277,7 @@ public static Filter fromJSON(@NonNull JSONObject json) { } else { return null; // WTF? } - serviceUpdateFilter.cacheOnly = JSONUtils.optBoolean(json, JSON_CACHE_ONLY); - serviceUpdateFilter.inFocus = JSONUtils.optBoolean(json, JSON_IN_FOCUS); - if (json.has(JSON_CACHE_VALIDITY_IN_MS)) { - serviceUpdateFilter.cacheValidityInMs = json.getLong(JSON_CACHE_VALIDITY_IN_MS); - } - if (json.has(JSON_PROVIDED_ENCRYPT_KEYS_MAP)) { - serviceUpdateFilter.providedEncryptKeysMap = JSONUtils.toMapOfStrings(json.getJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)); - } + ProviderContract.Filter.fromJSON(serviceUpdateFilter, json); return serviceUpdateFilter; } catch (JSONException jsone) { MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); @@ -404,14 +292,15 @@ public String toJSONString() { @Nullable public static String toJSONString(@NonNull Filter serviceUpdateFilter) { - JSONObject json = toJSON(serviceUpdateFilter); + final JSONObject json = toJSON(serviceUpdateFilter); return json == null ? null : json.toString(); } @Nullable public static JSONObject toJSON(@NonNull Filter serviceUpdateFilter) { try { - JSONObject json = new JSONObject(); + final JSONObject json = new JSONObject(); + ProviderContract.Filter.toJSON(serviceUpdateFilter, json); if (serviceUpdateFilter.poi != null) { json.put(JSON_POI, serviceUpdateFilter.poi.toJSON()); } @@ -424,18 +313,6 @@ public static JSONObject toJSON(@NonNull Filter serviceUpdateFilter) { if (serviceUpdateFilter.authority != null) { json.put(JSON_AUTHORITY, serviceUpdateFilter.authority); } - if (serviceUpdateFilter.getCacheOnlyOrNull() != null) { - json.put(JSON_CACHE_ONLY, serviceUpdateFilter.getCacheOnlyOrNull()); - } - if (serviceUpdateFilter.getInFocusOrNull() != null) { - json.put(JSON_IN_FOCUS, serviceUpdateFilter.getInFocusOrNull()); - } - if (serviceUpdateFilter.getCacheValidityInMsOrNull() != null) { - json.put(JSON_CACHE_VALIDITY_IN_MS, serviceUpdateFilter.getCacheValidityInMsOrNull()); - } - if (serviceUpdateFilter.getProvidedEncryptKeysMap() != null) { - json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(serviceUpdateFilter.getProvidedEncryptKeysMap())); - } return json; } catch (JSONException jsone) { MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", serviceUpdateFilter); diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java b/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java index 9eefc5ab..ec2c46ab 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java @@ -116,7 +116,7 @@ private static Cursor getStatus(@NonNull StatusProviderContract provider, @Nulla } // 3 - check if usable cache still valid (or if it could be refreshed) long cacheValidityInMs = provider.getStatusValidityInMs(statusFilter.isInFocusOrDefault()); - Long filterCacheValidityInMs = statusFilter.getCacheValidityInMsOrNull(); + Long filterCacheValidityInMs = statusFilter.getCacheValidityInMs(); if (filterCacheValidityInMs != null && filterCacheValidityInMs > provider.getMinDurationBetweenRefreshInMs(statusFilter.isInFocusOrDefault())) { cacheValidityInMs = filterCacheValidityInMs; } diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java index a4489f55..0ba56273 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java @@ -8,15 +8,11 @@ import org.json.JSONException; import org.json.JSONObject; -import org.mtransit.android.commons.JSONUtils; import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.SecureStringUtils; +import org.mtransit.android.commons.data.POI; import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.provider.common.ProviderContract; -import java.util.HashMap; -import java.util.Map; - public interface StatusProviderContract extends ProviderContract { String STATUS_PATH = "status"; @@ -68,7 +64,7 @@ class Columns { } @SuppressWarnings("WeakerAccess") - abstract class Filter implements MTLog.Loggable { + abstract class Filter extends ProviderContract.Filter implements MTLog.Loggable { private static final String LOG_TAG = StatusProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); @@ -78,23 +74,12 @@ public String getLogTag() { return LOG_TAG; } - private static final boolean CACHE_ONLY_DEFAULT = false; - - private static final boolean IN_FOCUS_DEFAULT = false; - @NonNull private String targetUUID; + @POI.ItemStatusType private int type; - @Nullable - private Boolean cacheOnly = null; - @Nullable - private Long cacheValidityInMs = null; - @Nullable - private Boolean inFocus = null; - @Nullable - private Map providedEncryptKeysMap = null; - public Filter(int type, @NonNull String targetUUID) { + public Filter(@POI.ItemStatusType int type, @NonNull String targetUUID) { this.type = type; this.targetUUID = targetUUID; } @@ -104,85 +89,11 @@ public String getTargetUUID() { return this.targetUUID; } + @POI.ItemStatusType public int getType() { return this.type; } - public boolean isCacheOnlyOrDefault() { - return this.cacheOnly == null ? CACHE_ONLY_DEFAULT : this.cacheOnly; - } - - @Nullable - public Boolean getCacheOnlyOrNull() { - return this.cacheOnly; - } - - public void setInFocus(@Nullable Boolean inFocus) { - this.inFocus = inFocus; - } - - public boolean isInFocusOrDefault() { - return this.inFocus == null ? IN_FOCUS_DEFAULT : this.inFocus; - } - - @Nullable - public Boolean getInFocusOrNull() { - return this.inFocus; - } - - @Nullable - public Long getCacheValidityInMsOrNull() { - return this.cacheValidityInMs; - } - - @SuppressWarnings("unused") - public boolean hasCacheValidityInMs() { - return cacheValidityInMs != null && cacheValidityInMs > 0; - } - - @SuppressWarnings("unused") - public void setCacheValidityInMs(@Nullable Long cacheValidityInMs) { - this.cacheValidityInMs = cacheValidityInMs; - } - - @NonNull - public Filter setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { - this.providedEncryptKeysMap = providedEncryptKeysMap; - return this; - } - - public boolean hasProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap != null && !this.providedEncryptKeysMap.isEmpty(); - } - - @Nullable - public Map getProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap; - } - - @Nullable - public String getProvidedEncryptKey(@NonNull String key) { - if (this.providedEncryptKeysMap == null) { - return null; - } - final String value = this.providedEncryptKeysMap.get(key); - if (value == null || value.trim().isEmpty()) { - return null; - } - return value; - } - - @NonNull - public Filter appendProvidedKeys(@Nullable Map keysMap) { - final Map providedEncryptKeysMap = new HashMap<>(); - if (keysMap != null) { - for (Map.Entry entry : keysMap.entrySet()) { - providedEncryptKeysMap.put(entry.getKey(), SecureStringUtils.enc(entry.getValue())); - } - } - return setProvidedEncryptKeysMap(providedEncryptKeysMap); - } - public static int getTypeFromJSONString(@Nullable String jsonString) { try { return jsonString == null ? -1 : getTypeFromJSON(new JSONObject(jsonString)); @@ -198,54 +109,22 @@ public static int getTypeFromJSON(@NonNull JSONObject json) throws JSONException @NonNull public static String getTargetUUIDFromJSON(@NonNull JSONObject json) throws JSONException { - return json.getString(JSON_TARGET); - } - - @Nullable - @SuppressWarnings("unused") - public static Long getCacheValidityInMsFromJSON(@NonNull JSONObject json) throws JSONException { - return json.has(JSON_CACHE_VALIDITY_IN_MS) ? json.getLong(JSON_CACHE_VALIDITY_IN_MS) : null; + return json.getString(JSON_TARGET_UUID); } public static void toJSON(@NonNull Filter statusFilter, @NonNull JSONObject json) throws JSONException { + ProviderContract.Filter.toJSON(statusFilter, json); json.put(JSON_TYPE, statusFilter.getType()); - json.put(JSON_TARGET, statusFilter.getTargetUUID()); - if (statusFilter.getCacheOnlyOrNull() != null) { - json.put(JSON_CACHE_ONLY, statusFilter.getCacheOnlyOrNull()); - } - if (statusFilter.getInFocusOrNull() != null) { - json.put(JSON_IN_FOCUS, statusFilter.getInFocusOrNull()); - } - if (statusFilter.getCacheValidityInMsOrNull() != null) { - json.put(JSON_CACHE_VALIDITY_IN_MS, statusFilter.getCacheValidityInMsOrNull()); - } - if (statusFilter.getProvidedEncryptKeysMap() != null) { - json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(statusFilter.getProvidedEncryptKeysMap())); - } + json.put(JSON_TARGET_UUID, statusFilter.getTargetUUID()); } private static final String JSON_TYPE = "type"; - private static final String JSON_TARGET = "target"; - private static final String JSON_CACHE_ONLY = "cacheOnly"; - private static final String JSON_IN_FOCUS = "inFocus"; - private static final String JSON_CACHE_VALIDITY_IN_MS = "cacheValidityInMs"; - private static final String JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap"; + private static final String JSON_TARGET_UUID = "target"; public static void fromJSON(@NonNull Filter statusFilter, @NonNull JSONObject json) throws JSONException { + ProviderContract.Filter.fromJSON(statusFilter, json); statusFilter.type = json.getInt(JSON_TYPE); - statusFilter.targetUUID = json.getString(JSON_TARGET); - if (json.has(JSON_CACHE_ONLY)) { - statusFilter.cacheOnly = json.getBoolean(JSON_CACHE_ONLY); - } - if (json.has(JSON_IN_FOCUS)) { - statusFilter.inFocus = json.getBoolean(JSON_IN_FOCUS); - } - if (json.has(JSON_CACHE_VALIDITY_IN_MS)) { - statusFilter.cacheValidityInMs = json.getLong(JSON_CACHE_VALIDITY_IN_MS); - } - if (json.has(JSON_PROVIDED_ENCRYPT_KEYS_MAP)) { - statusFilter.providedEncryptKeysMap = JSONUtils.toMapOfStrings(json.getJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)); - } + statusFilter.targetUUID = json.getString(JSON_TARGET_UUID); } @Nullable @@ -262,9 +141,7 @@ public String toString() { return Filter.class.getSimpleName() + "{" + "targetUUID='" + targetUUID + '\'' + ", type=" + type + - ", cacheOnly=" + cacheOnly + - ", cacheValidityInMs=" + cacheValidityInMs + - ", inFocus=" + inFocus + + super.toStringParts() + '}'; } } 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 97cfe75e..7f7a4563 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 @@ -144,7 +144,7 @@ object GTFSRealTimeVehiclePositionsProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.getNew(filter: VehicleLocationProviderContract.Filter): List? { - updateAgencyDataIfRequired(filter.inFocusOrDefault) + updateAgencyDataIfRequired(filter.isInFocusOrDefault) return getCached(filter) } diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt index ce7a3d24..00ff1a01 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt @@ -94,7 +94,7 @@ object NextBusVehicleLocationsProvider { @JvmStatic fun NextBusProvider.getNew(filter: VehicleLocationProviderContract.Filter): List? { - updateAgencyDataIfRequired(filter.inFocusOrDefault) + updateAgencyDataIfRequired(filter.isInFocusOrDefault) return getCached(filter) } diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt index 55530b51..62611fa0 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt @@ -72,13 +72,13 @@ abstract class VehicleLocationProvider : MTContentProvider(), } } } - if (filter.cacheOnlyOrDefault) { + if (filter.isCacheOnlyOrDefault) { if (cachedVehicleLocations.isNullOrEmpty()) { MTLog.w(this, "getVehicleLocations() > No useful cache found!") } return getVehicleLocationCursor(cachedVehicleLocations) } - val cacheValidityInMs = getVehicleLocationValidityInMs(filter.inFocusOrDefault) + val cacheValidityInMs = getVehicleLocationValidityInMs(filter.isInFocusOrDefault) // TODO filter cache validity override like service update? var loadNewVehicleLocations = false if (cachedVehicleLocations.isNullOrEmpty()) { 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 28c9e99b..6ffcc7d6 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 @@ -8,7 +8,6 @@ import org.json.JSONException import org.json.JSONObject import org.mtransit.android.commons.JSONUtils 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 @@ -18,7 +17,6 @@ import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation -import org.mtransit.commons.mapNotNullToMap interface VehicleLocationProviderContract : ProviderContract { @@ -95,16 +93,7 @@ interface VehicleLocationProviderContract : ProviderContract { override val poi: POI? = null, // RouteDirectionStop or DefaultPOI override val route: Route? = null, override val routeDirection: RouteDirection? = null, - ) : GTFSRealTimeProviderFilter, MTLog.Loggable { - - var inFocus: Boolean? = null - val inFocusOrDefault get() = inFocus ?: false - - var cacheOnly: Boolean? = null - val cacheOnlyOrDefault get() = cacheOnly ?: false - - var providedEncryptKeysMap: Map? = null - private set + ) : ProviderContract.Filter(), GTFSRealTimeProviderFilter, MTLog.Loggable { @SuppressLint("DiscouragedApi") constructor(poi: POI) : @@ -118,19 +107,6 @@ interface VehicleLocationProviderContract : ProviderContract { constructor(routeDirection: RouteDirection) : this(authority = routeDirection.authority, routeDirection = routeDirection) - @Suppress("unused") // main app only - fun appendProvidedKeys(keysMap: Map?): Filter { - keysMap?.mapNotNullToMap { (key, value) -> - SecureStringUtils.enc(value)?.let { encValue -> key to encValue } - }?.let { - providedEncryptKeysMap = it - } - return this - } - - fun getProvidedEncryptKey(key: String) = - this.providedEncryptKeysMap?.get(key)?.takeIf { it.isNotBlank() } - companion object { private val LOG_TAG: String = VehicleLocationProviderContract::class.java.simpleName + ">" + Filter::class.java.simpleName @@ -138,9 +114,6 @@ interface VehicleLocationProviderContract : ProviderContract { private const val JSON_POI = "poi" private const val JSON_ROUTE = "route" private const val JSON_ROUTE_DIRECTION = "routeDirection" - private const val JSON_CACHE_ONLY = "cacheOnly" - private const val JSON_IN_FOCUS = "inFocus" - private const val JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap" fun fromJSONString(jsonString: String?): Filter? { try { @@ -163,18 +136,11 @@ interface VehicleLocationProviderContract : ProviderContract { val routeDirection = json.optJSONObject(JSON_ROUTE_DIRECTION)?.let { jRouteDirection -> authority?.let { RouteDirection.fromJSON(jRouteDirection, it) } } - val inFocus = JSONUtils.optBoolean(json, JSON_IN_FOCUS) - val cacheOnly = JSONUtils.optBoolean(json, JSON_CACHE_ONLY) - val providedEncryptKeysMap: Map? = json.optJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)?.let { jProvidedEncryptKeysMap -> - JSONUtils.toMapOfStrings(jProvidedEncryptKeysMap) - } return (poi?.let { Filter(authority = it.authority, poi = it) } ?: route?.let { Filter(authority = route.authority, route = it) } ?: routeDirection?.let { Filter(authority = routeDirection.authority, routeDirection = it) }) ?.apply { - this.inFocus = inFocus - this.cacheOnly = cacheOnly - this.providedEncryptKeysMap = providedEncryptKeysMap + fromJSON(this, json) } } @@ -184,13 +150,11 @@ interface VehicleLocationProviderContract : ProviderContract { fun toJSON(vehicleLocationFilter: Filter): JSONObject? { return try { JSONObject().apply { + toJSON(vehicleLocationFilter, this) put(JSON_AUTHORITY, vehicleLocationFilter.authority) vehicleLocationFilter.poi?.let { put(JSON_POI, it.toJSON()) } vehicleLocationFilter.route?.let { put(JSON_ROUTE, Route.toJSON(it)) } vehicleLocationFilter.routeDirection?.let { put(JSON_ROUTE_DIRECTION, RouteDirection.toJSON(it)) } - vehicleLocationFilter.inFocus?.let { put(JSON_IN_FOCUS, it) } - vehicleLocationFilter.cacheOnly?.let { put(JSON_CACHE_ONLY, it) } - vehicleLocationFilter.providedEncryptKeysMap?.let { put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(it)) } } } catch (jsone: JSONException) { MTLog.w(LOG_TAG, jsone, "Error while making JSON object '$vehicleLocationFilter'!") From f81cfdb84f236010b9b7fed4d03b6cef3033f1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 25 Jun 2026 10:06:46 -0400 Subject: [PATCH 02/12] wip --- .../provider/common/ProviderContract.java | 2 +- .../provider/news/NewsProviderContract.java | 84 +++++++++---------- 2 files changed, 41 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java index a0c20c04..423321ae 100644 --- a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java @@ -111,7 +111,7 @@ public String getProvidedEncryptKey(@NonNull String key) { return value; } - public void appendProvidedKeys(@Nullable Map keysMap) { + public void setProvidedKeys(@Nullable Map keysMap) { final Map providedEncryptKeysMap = new HashMap<>(); if (keysMap != null) { for (Map.Entry entry : keysMap.entrySet()) { diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java index 13b817a6..bb968f5f 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java @@ -11,9 +11,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.mtransit.android.commons.JSONUtils; import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.SecureStringUtils; import org.mtransit.android.commons.SqlUtils; import org.mtransit.android.commons.data.News; import org.mtransit.android.commons.data.POI; @@ -24,9 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; public interface NewsProviderContract extends ProviderContract { @@ -146,9 +142,9 @@ public String getLogTag() { } @Nullable - private List articleUUIDs; // article UUIDs + private List articlesUUIDs; // article UUIDs @Nullable - private List targetUUIDs; // POI UUIDs + private List targetsUUIDs; // POI UUIDs @Nullable private Long minCreatedAtInMs = null; @@ -161,69 +157,69 @@ public static Filter getNewEmptyFilter() { } @NonNull - public static Filter getNewArticleUUIDFilter(@NonNull String uuid) { - return getNewArticleUUIDsFilter(Collections.singletonList(uuid)); + public static Filter getNewArticleUUIDFilter(@NonNull String articleUUID) { + return getNewArticlesUUIDsFilter(Collections.singletonList(articleUUID)); } @NonNull - public static Filter getNewArticleUUIDsFilter(@Nullable List uuids) { - return new Filter().setArticleUUIDs(uuids); + public static Filter getNewArticlesUUIDsFilter(@Nullable List articlesUUIDs) { + return new Filter().setArticlesUUIDs(articlesUUIDs); } @NonNull - private Filter setArticleUUIDs(@Nullable List articleUUIDs) { - if (articleUUIDs == null || articleUUIDs.isEmpty()) { + private Filter setArticlesUUIDs(@Nullable List articlesUUIDs) { + if (articlesUUIDs == null || articlesUUIDs.isEmpty()) { throw new UnsupportedOperationException("Need at least 1 article uuid!"); } - this.articleUUIDs = articleUUIDs; + this.articlesUUIDs = articlesUUIDs; return this; } @SuppressWarnings("unused") @Nullable - public List getArticleUUIDs() { - return articleUUIDs; + public List getArticlesUUIDs() { + return articlesUUIDs; } @NonNull public static Filter getNewTargetUUIDsFilter(@NonNull POI poi) { - return getNewTargetsUUIDsFilter(makeTargetUUIDs(poi)); + return getNewTargetsUUIDsFilter(makeTargetsUUIDs(poi)); } @NonNull - public static ArrayList makeTargetUUIDs(@NonNull POI poi) { - final ArrayList targets = new ArrayList<>(); - targets.add(poi.getAuthority()); + public static ArrayList makeTargetsUUIDs(@NonNull POI poi) { + final ArrayList targetsUUIDs = new ArrayList<>(); + targetsUUIDs.add(poi.getAuthority()); if (poi instanceof RouteDirectionStop) { - targets.add(POI.POIUtils.makeUUID(poi.getAuthority(), ((RouteDirectionStop) poi).getRoute().getId())); + targetsUUIDs.add(POI.POIUtils.makeUUID(poi.getAuthority(), ((RouteDirectionStop) poi).getRoute().getId())); } - return targets; + return targetsUUIDs; } @SuppressWarnings("unused") @NonNull - public static Filter getNewTargetUUIDsFilter(@NonNull String targets) { - return getNewArticleUUIDsFilter(Collections.singletonList(targets)); + public static Filter getNewTargetUUIDsFilter(@NonNull String targetUUID) { + return getNewTargetsUUIDsFilter(Collections.singletonList(targetUUID)); } @NonNull - public static Filter getNewTargetsUUIDsFilter(@Nullable List targets) { - return new Filter().setTargetUUIDs(targets); + public static Filter getNewTargetsUUIDsFilter(@Nullable List targetsUUIDs) { + return new Filter().setTargetsUUIDs(targetsUUIDs); } @NonNull - private Filter setTargetUUIDs(List targetUUIDs) { - if (targetUUIDs == null || targetUUIDs.isEmpty()) { + private Filter setTargetsUUIDs(List targetsUUIDs) { + if (targetsUUIDs == null || targetsUUIDs.isEmpty()) { throw new UnsupportedOperationException("Need at least 1 target!"); } - this.targetUUIDs = targetUUIDs; + this.targetsUUIDs = targetsUUIDs; return this; } @SuppressWarnings("unused") @Nullable - public List getTargetUUIDs() { - return targetUUIDs; + public List getTargetsUUIDs() { + return targetsUUIDs; } @NonNull @@ -242,9 +238,9 @@ public Long getMinCreatedAtInMsOrNull() { public String toString() { StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); if (isUUIDFilter(this)) { - sb.append("articleUUIDs:").append(this.articleUUIDs).append(','); + sb.append("articleUUIDs:").append(this.articlesUUIDs).append(','); } else if (isTargetFilter(this)) { - sb.append("targetsUUIDs:").append(this.targetUUIDs).append(','); + sb.append("targetsUUIDs:").append(this.targetsUUIDs).append(','); } sb.append(super.toStringParts()); sb.append("minCreatedAtInMs:").append(this.minCreatedAtInMs); @@ -257,29 +253,29 @@ public String toString() { public String toStringTargetsAndUuid() { final StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); if (isUUIDFilter(this)) { - sb.append("articleUUIDs:").append(this.articleUUIDs).append(','); + sb.append("articleUUIDs:").append(this.articlesUUIDs).append(','); } else if (isTargetFilter(this)) { - sb.append("targetsUUIDs:").append(this.targetUUIDs).append(','); + sb.append("targetsUUIDs:").append(this.targetsUUIDs).append(','); } sb.append(']'); return sb.toString(); } public static boolean isUUIDFilter(@Nullable Filter newsFilter) { - return newsFilter != null && CollectionUtils.getSize(newsFilter.articleUUIDs) > 0; + return newsFilter != null && CollectionUtils.getSize(newsFilter.articlesUUIDs) > 0; } public static boolean isTargetFilter(@Nullable Filter newsFilter) { - return newsFilter != null && CollectionUtils.getSize(newsFilter.targetUUIDs) > 0; + return newsFilter != null && CollectionUtils.getSize(newsFilter.targetsUUIDs) > 0; } @NonNull public String getSqlSelection(@NonNull String uuidTableColumn, @NonNull String targetColumn, @NonNull String createdAtColumn) { StringBuilder sb = new StringBuilder(); if (isUUIDFilter(this)) { - sb.append(SqlUtils.getWhereInString(uuidTableColumn, this.articleUUIDs)); + sb.append(SqlUtils.getWhereInString(uuidTableColumn, this.articlesUUIDs)); } else if (isTargetFilter(this)) { - sb.append(SqlUtils.getWhereInString(targetColumn, this.targetUUIDs)); + sb.append(SqlUtils.getWhereInString(targetColumn, this.targetsUUIDs)); } if (getMinCreatedAtInMsOrNull() != null) { if (sb.length() > 0) { @@ -316,13 +312,13 @@ public static Filter fromJSON(@NonNull JSONObject json) { for (int i = 0; i < jArticleUUIDs.length(); i++) { articleUUIDs.add(jArticleUUIDs.getString(i)); } - newsFilter.setArticleUUIDs(articleUUIDs); + newsFilter.setArticlesUUIDs(articleUUIDs); } else if (jTargetsUUIDs != null && jTargetsUUIDs.length() > 0) { final ArrayList targetsUUIDs = new ArrayList<>(); for (int i = 0; i < jTargetsUUIDs.length(); i++) { targetsUUIDs.add(jTargetsUUIDs.getString(i)); } - newsFilter.setTargetUUIDs(targetsUUIDs); + newsFilter.setTargetsUUIDs(targetsUUIDs); } if (json.has(JSON_MIN_CREATED_AT_IN_MS)) { newsFilter.minCreatedAtInMs = json.getLong(JSON_MIN_CREATED_AT_IN_MS); @@ -353,15 +349,15 @@ public static JSONObject toJSON(@NonNull Filter newsFilter) { if (newsFilter.getMinCreatedAtInMsOrNull() != null) { json.put(JSON_MIN_CREATED_AT_IN_MS, newsFilter.getMinCreatedAtInMsOrNull()); } - if (isUUIDFilter(newsFilter) && newsFilter.articleUUIDs != null) { + if (isUUIDFilter(newsFilter) && newsFilter.articlesUUIDs != null) { final JSONArray jArticleUUIDs = new JSONArray(); - for (String articleUUID : newsFilter.articleUUIDs) { + for (String articleUUID : newsFilter.articlesUUIDs) { jArticleUUIDs.put(articleUUID); } json.put(JSON_ARTICLE_UUIDS, jArticleUUIDs); - } else if (isTargetFilter(newsFilter) && newsFilter.targetUUIDs != null) { + } else if (isTargetFilter(newsFilter) && newsFilter.targetsUUIDs != null) { final JSONArray jTargetUUIDs = new JSONArray(); - for (String targetUUID : newsFilter.targetUUIDs) { + for (String targetUUID : newsFilter.targetsUUIDs) { jTargetUUIDs.put(targetUUID); } json.put(JSON_TARGETS_UUIDS, jTargetUUIDs); From 0a1e443ba5c089de19b62ded34e14f4132716c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 25 Jun 2026 10:26:55 -0400 Subject: [PATCH 03/12] wip --- .../mtransit/android/commons/data/AppStatus.java | 12 +++++++++--- .../commons/data/AvailabilityPercent.java | 6 ++++++ .../mtransit/android/commons/data/Schedule.java | 6 ++++++ .../provider/common/ProviderContract.java | 16 ++++++++++++---- .../provider/news/NewsProviderContract.java | 1 + .../ServiceUpdateProviderContract.java | 1 + .../VehicleLocationProviderContract.kt | 2 +- 7 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/AppStatus.java b/src/main/java/org/mtransit/android/commons/data/AppStatus.java index 23cd748a..c6a4d306 100644 --- a/src/main/java/org/mtransit/android/commons/data/AppStatus.java +++ b/src/main/java/org/mtransit/android/commons/data/AppStatus.java @@ -247,6 +247,12 @@ public static StatusProviderContract.Filter fromJSON(@NonNull JSONObject json) { } } + @Nullable + @Override + public String toJSONString() { + return toJSONString(this); + } + @Nullable @Override public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFilter) { @@ -255,17 +261,17 @@ public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFi @Nullable private static String toJSONString(@NonNull StatusProviderContract.Filter statusFilter) { - JSONObject json = toJSON(statusFilter); + final JSONObject json = toJSON(statusFilter); return json == null ? null : json.toString(); } @Nullable private static JSONObject toJSON(@NonNull StatusProviderContract.Filter statusFilter) { try { - JSONObject json = new JSONObject(); + final JSONObject json = new JSONObject(); StatusProviderContract.Filter.toJSON(statusFilter, json); if (statusFilter instanceof AppStatusFilter) { - AppStatusFilter appStatusFilter = (AppStatusFilter) statusFilter; + final AppStatusFilter appStatusFilter = (AppStatusFilter) statusFilter; json.put(JSON_PKG, appStatusFilter.pkg); } return json; diff --git a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java index 19c8c9d9..4ddad1fd 100644 --- a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java +++ b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java @@ -652,6 +652,12 @@ public static StatusProviderContract.Filter fromJSON(@NonNull JSONObject json) { } } + @Nullable + @Override + public String toJSONString() { + return toJSONString(this); + } + @Nullable @Override public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFilter) { diff --git a/src/main/java/org/mtransit/android/commons/data/Schedule.java b/src/main/java/org/mtransit/android/commons/data/Schedule.java index b5a44637..f6e8f3ac 100644 --- a/src/main/java/org/mtransit/android/commons/data/Schedule.java +++ b/src/main/java/org/mtransit/android/commons/data/Schedule.java @@ -1054,6 +1054,12 @@ public static StatusProviderContract.Filter fromJSON(@NonNull JSONObject json) { } } + @Nullable + @Override + public String toJSONString() { + return toJSONString(this); + } + @Nullable @Override public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFilter) { diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java index 423321ae..e43cbdd9 100644 --- a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java @@ -90,14 +90,16 @@ public void setInFocus(@Nullable Boolean inFocus) { this.inFocus = inFocus; } - public void setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { + void setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { this.providedEncryptKeysMap = providedEncryptKeysMap; } + @SuppressWarnings("unused") public boolean hasProvidedEncryptKeysMap() { return this.providedEncryptKeysMap != null && !this.providedEncryptKeysMap.isEmpty(); } + @SuppressWarnings("unused") @Nullable public Map getProvidedEncryptKeysMap() { return this.providedEncryptKeysMap; @@ -115,7 +117,9 @@ public void setProvidedKeys(@Nullable Map keysMap) { final Map providedEncryptKeysMap = new HashMap<>(); if (keysMap != null) { for (Map.Entry entry : keysMap.entrySet()) { - providedEncryptKeysMap.put(entry.getKey(), SecureStringUtils.enc(entry.getValue())); + final String enc = SecureStringUtils.enc(entry.getValue()); + if (enc == null) continue; + providedEncryptKeysMap.put(entry.getKey(), enc); } } setProvidedEncryptKeysMap(providedEncryptKeysMap); @@ -132,6 +136,10 @@ public static Long getCacheValidityInMsFromJSON(@NonNull JSONObject json) throws return json.has(JSON_CACHE_VALIDITY_IN_MS) ? json.getLong(JSON_CACHE_VALIDITY_IN_MS) : null; } + @SuppressWarnings("unused") + @Nullable + public abstract String toJSONString(); + public static void toJSON(@NonNull Filter filter, @NonNull JSONObject json) throws JSONException { if (filter.cacheOnly != null) { json.put(JSON_CACHE_ONLY, filter.cacheOnly); @@ -142,8 +150,8 @@ public static void toJSON(@NonNull Filter filter, @NonNull JSONObject json) thro if (filter.inFocus != null) { json.put(JSON_IN_FOCUS, filter.inFocus); } - if (filter.getProvidedEncryptKeysMap() != null) { - json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(filter.getProvidedEncryptKeysMap())); + if (filter.providedEncryptKeysMap != null) { + json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(filter.providedEncryptKeysMap)); } } diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java index bb968f5f..12100ef7 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java @@ -331,6 +331,7 @@ public static Filter fromJSON(@NonNull JSONObject json) { } @Nullable + @Override public String toJSONString() { return toJSONString(this); } 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 b1da8789..8cc599c7 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 @@ -286,6 +286,7 @@ public static Filter fromJSON(@NonNull JSONObject json) { } @Nullable + @Override public String toJSONString() { return toJSONString(this); } 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 6ffcc7d6..fa08b1f7 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 @@ -166,7 +166,7 @@ interface VehicleLocationProviderContract : ProviderContract { override fun getLogTag() = LOG_TAG @Suppress("unused") // used from main app - fun toJSONString() = toJSONString(this) + override fun toJSONString() = toJSONString(this) private val _route: Route? get() = (poi as? RouteDirectionStop)?.route From 8cc3d7a5f7386b65041785ef102222d418ba3d89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 25 Jun 2026 10:38:20 -0400 Subject: [PATCH 04/12] wip --- .../commons/data/AvailabilityPercent.java | 4 ++-- .../provider/poi/POIProviderContract.java | 16 ++++++++++++++-- .../provider/status/StatusProviderContract.java | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java index 4ddad1fd..c58a31f3 100644 --- a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java +++ b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java @@ -666,14 +666,14 @@ public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFi @Nullable private static String toJSONString(@NonNull StatusProviderContract.Filter statusFilter) { - JSONObject json = toJSON(statusFilter); + final JSONObject json = toJSON(statusFilter); return json == null ? null : json.toString(); } @Nullable private static JSONObject toJSON(@NonNull StatusProviderContract.Filter statusFilter) { try { - JSONObject json = new JSONObject(); + final JSONObject json = new JSONObject(); StatusProviderContract.Filter.toJSON(statusFilter, json); return json; } catch (JSONException jsone) { diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java index 6d6f4179..a30551d7 100644 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java @@ -605,10 +605,22 @@ private static Filter fromJSON(JSONObject json) { private static final String JSON_EXTRAS_KEY = "key"; private static final String JSON_EXTRAS_VALUE = "value"; + @Nullable + @Override + public String toJSONString() { + return toJSONString(this); + } + + @Nullable + private static String toJSONString(@NonNull Filter statusFilter) { + final JSONObject json = toJSON(statusFilter); + return json == null ? null : json.toString(); + } + @Nullable public static JSONObject toJSON(@Nullable Filter poiFilter) { try { - JSONObject json = new JSONObject(); + final JSONObject json = new JSONObject(); if (isAreaFilter(poiFilter)) { json.put(JSON_LAT, poiFilter.lat); json.put(JSON_LNG, poiFilter.lng); @@ -647,7 +659,7 @@ public static JSONObject toJSON(@Nullable Filter poiFilter) { } else { MTLog.w(LOG_TAG, "Empty POI filter '%s' converted to JSON!", poiFilter); } - JSONArray jExtras = new JSONArray(); + final JSONArray jExtras = new JSONArray(); if (poiFilter != null) { ProviderContract.Filter.toJSON(poiFilter, json); for (int i = 0; i < poiFilter.extras.size(); i++) { diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java index 0ba56273..43e23d77 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java @@ -141,7 +141,7 @@ public String toString() { return Filter.class.getSimpleName() + "{" + "targetUUID='" + targetUUID + '\'' + ", type=" + type + - super.toStringParts() + + ", " + super.toStringParts() + '}'; } } From f45efec64fa89fb7373a9f114caabd9956fccde1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Thu, 25 Jun 2026 10:39:24 -0400 Subject: [PATCH 05/12] clean --- .../android/commons/provider/common/ProviderContractExt.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/java/org/mtransit/android/commons/provider/common/ProviderContractExt.kt diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContractExt.kt b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContractExt.kt deleted file mode 100644 index e69de29b..00000000 From f6d92b3e0695ce896ce804c3b8e5c528b350b6df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 13:07:58 -0400 Subject: [PATCH 06/12] wip --- .../org/mtransit/android/commons/JSONExt.kt | 19 + .../android/commons/data/AppStatus.java | 90 --- .../android/commons/data/AppStatusFilter.kt | 80 ++ .../commons/data/AvailabilityPercent.java | 75 +- .../data/AvailabilityPercentStatusFilter.kt | 75 ++ .../android/commons/data/POIStatus.java | 2 +- .../android/commons/data/Schedule.java | 218 ------ .../commons/data/ScheduleStatusFilter.kt | 165 ++++ .../android/commons/data/ServiceUpdateKtx.kt | 4 +- .../provider/CleverDevicesProvider.java | 9 +- .../commons/provider/GBFSProvider.java | 8 +- .../commons/provider/GTFSProvider.java | 8 +- .../provider/GTFSProviderContract.java | 4 +- .../provider/GTFSRealTimeProvider.java | 2 +- .../commons/provider/InstagramNewsProvider.kt | 21 +- .../commons/provider/NextBusProvider.java | 31 +- .../commons/provider/OneBusAwayProvider.java | 10 +- .../commons/provider/RSSNewsProvider.java | 3 +- .../commons/provider/RTCQuebecProvider.java | 11 +- .../provider/ReginaTransitProvider.java | 9 +- .../commons/provider/StmInfoApiProvider.java | 11 +- .../commons/provider/TwitterNewsProvider.kt | 33 +- .../provider/WinnipegTransitProvider.java | 14 +- .../commons/provider/YouTubeNewsProvider.kt | 22 +- .../agency/AgencyProviderContract.java | 2 + .../provider/bike/BikeStationProvider.java | 20 +- .../provider/common/ProviderContract.java | 182 ----- .../provider/common/ProviderContract.kt | 100 +++ .../provider/gtfs/GTFSPOIProvider.java | 8 +- .../provider/gtfs/GTFSStatusProvider.java | 9 +- .../commons/provider/gtfs/GTFSStringsUtils.kt | 1 + .../provider/gtfs/GtfsStatusProviderExt.kt | 12 +- .../commons/provider/news/NewsProvider.java | 8 +- .../provider/news/NewsProviderContract.java | 373 --------- .../provider/news/NewsProviderContract.kt | 279 +++++++ .../commons/provider/poi/POIProvider.java | 31 +- .../provider/poi/POIProviderContract.java | 706 ------------------ .../provider/poi/POIProviderContract.kt | 528 +++++++++++++ .../serviceupdate/ServiceUpdateProvider.java | 2 +- .../ServiceUpdateProviderContract.java | 324 -------- .../ServiceUpdateProviderContract.kt | 222 ++++++ .../serviceupdate/ServiceUpdateProviderExt.kt | 2 +- .../status/GTFSRealTimeTripUpdatesProvider.kt | 9 +- .../provider/status/StatusProvider.java | 11 +- .../status/StatusProviderContract.java | 148 ---- .../provider/status/StatusProviderContract.kt | 164 ++++ .../NextBusVehicleLocationsProvider.kt | 2 +- .../VehicleLocationProvider.kt | 12 +- .../VehicleLocationProviderContract.kt | 55 +- 49 files changed, 1841 insertions(+), 2293 deletions(-) create mode 100644 src/main/java/org/mtransit/android/commons/JSONExt.kt create mode 100644 src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt create mode 100644 src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt create mode 100644 src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt delete mode 100644 src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java create mode 100644 src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt delete mode 100644 src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java create mode 100644 src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt delete mode 100644 src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java create mode 100644 src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt delete mode 100644 src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java create mode 100644 src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt delete mode 100644 src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java create mode 100644 src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt diff --git a/src/main/java/org/mtransit/android/commons/JSONExt.kt b/src/main/java/org/mtransit/android/commons/JSONExt.kt new file mode 100644 index 00000000..f0720d18 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/JSONExt.kt @@ -0,0 +1,19 @@ +package org.mtransit.android.commons + +import org.json.JSONObject + +fun JSONObject.optLong(name: String, fallback: Long? = null) = + takeIf { it.has(name) }?.getLong(name) ?: fallback + +fun JSONObject.optInt(name: String, fallback: Int? = null) = + takeIf { it.has(name) }?.optInt(name) ?: fallback + +fun JSONObject.optDouble(name: String, fallback: Double? = null) = + takeIf { it.has(name) }?.optDouble(name) ?: fallback + +fun JSONObject.optBoolean(name: String, fallback: Boolean? = null) = + takeIf { it.has(name) }?.optBoolean(name) ?: fallback + +fun JSONObject.optString(name: String, fallback: String? = null) = + takeIf { it.has(name) }?.optString(name) ?: fallback + diff --git a/src/main/java/org/mtransit/android/commons/data/AppStatus.java b/src/main/java/org/mtransit/android/commons/data/AppStatus.java index c6a4d306..b7688ad9 100644 --- a/src/main/java/org/mtransit/android/commons/data/AppStatus.java +++ b/src/main/java/org/mtransit/android/commons/data/AppStatus.java @@ -191,94 +191,4 @@ public JSONObject getExtrasJSON() { return null; // no partial result } } - - public static class AppStatusFilter extends StatusProviderContract.Filter { - - private static final String LOG_TAG = AppStatusFilter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - @NonNull - private final String pkg; - - public AppStatusFilter(@NonNull String targetUUID, @NonNull String pkg) { - super(POI.ITEM_STATUS_TYPE_APP, targetUUID); - this.pkg = pkg; - } - - @NonNull - public String getPkg() { - return pkg; - } - - @Nullable - @Override - public StatusProviderContract.Filter fromJSONStringStatic(@Nullable String jsonString) { - return fromJSONString(jsonString); - } - - @Nullable - public static StatusProviderContract.Filter fromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? null : fromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return null; - } - } - - private static final String JSON_PKG = "pkg"; - - @Nullable - public static StatusProviderContract.Filter fromJSON(@NonNull JSONObject json) { - try { - String targetUUID = StatusProviderContract.Filter.getTargetUUIDFromJSON(json); - String pkg = json.getString(JSON_PKG); - AppStatusFilter appStatusFilter = new AppStatusFilter(targetUUID, pkg); - StatusProviderContract.Filter.fromJSON(appStatusFilter, json); - return appStatusFilter; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); - return null; - } - } - - @Nullable - @Override - public String toJSONString() { - return toJSONString(this); - } - - @Nullable - @Override - public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFilter) { - return toJSONString(statusFilter); - } - - @Nullable - private static String toJSONString(@NonNull StatusProviderContract.Filter statusFilter) { - final JSONObject json = toJSON(statusFilter); - return json == null ? null : json.toString(); - } - - @Nullable - private static JSONObject toJSON(@NonNull StatusProviderContract.Filter statusFilter) { - try { - final JSONObject json = new JSONObject(); - StatusProviderContract.Filter.toJSON(statusFilter, json); - if (statusFilter instanceof AppStatusFilter) { - final AppStatusFilter appStatusFilter = (AppStatusFilter) statusFilter; - json.put(JSON_PKG, appStatusFilter.pkg); - } - return json; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", statusFilter); - return null; - } - } - } } diff --git a/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt b/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt new file mode 100644 index 00000000..7a840339 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt @@ -0,0 +1,80 @@ +package org.mtransit.android.commons.data + +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.provider.status.StatusProviderContract + +data class AppStatusFilter( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, + override val targetUUID: String, + val pkg: String +) : StatusProviderContract.Filter(POI.ITEM_STATUS_TYPE_APP, targetUUID) { + + companion object { + private val LOG_TAG: String = AppStatusFilter::class.java.getSimpleName() + + @JvmStatic + fun from(targetUUID: String, pkg: String) = AppStatusFilter( + targetUUID = targetUUID, + pkg = pkg, + ) + + @JvmStatic + fun fromJSONString(jsonString: String?): StatusProviderContract.Filter? { + try { + return jsonString?.let { fromJSON(JSONObject(it)) } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '$jsonString'") + return null + } + } + + private const val JSON_PKG = "pkg" + + fun fromJSON(json: JSONObject): StatusProviderContract.Filter? { + try { + return AppStatusFilter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + targetUUID = getTargetUUIDFromJSON(json), + pkg = json.getString(JSON_PKG) + ) + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '$json'") + return null + } + } + + private fun toJSONString(statusFilter: StatusProviderContract.Filter) = toJSON(statusFilter)?.toString() + + private fun toJSON(statusFilter: StatusProviderContract.Filter): JSONObject? { + try { + return JSONObject().apply { + toJSON(statusFilter, this) + if (statusFilter is AppStatusFilter) { + put(JSON_PKG, statusFilter.pkg) + } + } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", statusFilter) + return null + } + } + } + + override fun getLogTag() = LOG_TAG + + override fun copyWith(providedEncryptKeysMap: Map?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) + + override fun fromJSONStringStatic(jsonString: String?) = fromJSONString(jsonString) + + override fun toJSONString() = toJSONString(this) + + override fun toJSONStringStatic(statusFilter: StatusProviderContract.Filter) = toJSONString(statusFilter) +} diff --git a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java index c58a31f3..795a4925 100644 --- a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java +++ b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercent.java @@ -608,78 +608,5 @@ public String toString() { ", statusMsgId=" + statusMsgId + '}'; } - - public static class AvailabilityPercentStatusFilter extends StatusProviderContract.Filter { - - private static final String LOG_TAG = AvailabilityPercentStatusFilter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - public AvailabilityPercentStatusFilter(@NonNull String targetUUID) { - super(POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT, targetUUID); - } - - @Nullable - @Override - public StatusProviderContract.Filter fromJSONStringStatic(@Nullable String jsonString) { - return fromJSONString(jsonString); - } - - @Nullable - public static StatusProviderContract.Filter fromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? null : fromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return null; - } - } - - @Nullable - public static StatusProviderContract.Filter fromJSON(@NonNull JSONObject json) { - try { - String targetUUID = StatusProviderContract.Filter.getTargetUUIDFromJSON(json); - AvailabilityPercentStatusFilter availabilityPercentStatusFilter = new AvailabilityPercentStatusFilter(targetUUID); - StatusProviderContract.Filter.fromJSON(availabilityPercentStatusFilter, json); - return availabilityPercentStatusFilter; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); - return null; - } - } - - @Nullable - @Override - public String toJSONString() { - return toJSONString(this); - } - - @Nullable - @Override - public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFilter) { - return toJSONString(statusFilter); - } - - @Nullable - private static String toJSONString(@NonNull StatusProviderContract.Filter statusFilter) { - final JSONObject json = toJSON(statusFilter); - return json == null ? null : json.toString(); - } - - @Nullable - private static JSONObject toJSON(@NonNull StatusProviderContract.Filter statusFilter) { - try { - final JSONObject json = new JSONObject(); - StatusProviderContract.Filter.toJSON(statusFilter, json); - return json; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", statusFilter); - return null; - } - } - } } + diff --git a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt new file mode 100644 index 00000000..2b35b880 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt @@ -0,0 +1,75 @@ +package org.mtransit.android.commons.data + +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.provider.status.StatusProviderContract + +data class AvailabilityPercentStatusFilter( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, + override val targetUUID: String, +) : StatusProviderContract.Filter(POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT, targetUUID) { + + companion object { + private val LOG_TAG: String = AvailabilityPercentStatusFilter::class.java.getSimpleName() + + @JvmStatic + fun from(poi: POI) = from(poi.uuid) + + @JvmStatic + fun from(targetUUID: String) = AvailabilityPercentStatusFilter( + targetUUID = targetUUID, + ) + + @JvmStatic + fun fromJSONString(jsonString: String?): StatusProviderContract.Filter? { + try { + return jsonString?.let { fromJSON(JSONObject(it)) } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '$jsonString'") + return null + } + } + + fun fromJSON(json: JSONObject): StatusProviderContract.Filter? { + try { + return AvailabilityPercentStatusFilter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + targetUUID = getTargetUUIDFromJSON(json), + ) + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '$json'") + return null + } + } + + private fun toJSONString(statusFilter: StatusProviderContract.Filter) = toJSON(statusFilter)?.toString() + + private fun toJSON(statusFilter: StatusProviderContract.Filter): JSONObject? { + try { + return JSONObject().apply { + toJSON(statusFilter, this) + } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while making JSON object '$statusFilter'") + return null + } + } + } + + override fun getLogTag() = LOG_TAG + + override fun copyWith(providedEncryptKeysMap: Map?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) + + override fun fromJSONStringStatic(jsonString: String?) = fromJSONString(jsonString) + + override fun toJSONString() = toJSONString(this) + + override fun toJSONStringStatic(statusFilter: StatusProviderContract.Filter) = toJSONString(statusFilter) +} diff --git a/src/main/java/org/mtransit/android/commons/data/POIStatus.java b/src/main/java/org/mtransit/android/commons/data/POIStatus.java index f9c2fb6f..d2f5d336 100644 --- a/src/main/java/org/mtransit/android/commons/data/POIStatus.java +++ b/src/main/java/org/mtransit/android/commons/data/POIStatus.java @@ -123,7 +123,7 @@ public static POIStatus fromCursor(@NonNull Cursor cursor) { @NonNull public Cursor toCursor() { - MatrixCursor cursor = new MatrixCursor(StatusProviderContract.PROJECTION_STATUS); + final MatrixCursor cursor = new MatrixCursor(StatusProviderContract.getPROJECTION_STATUS()); cursor.addRow(new Object[]{ this.id, this.type, diff --git a/src/main/java/org/mtransit/android/commons/data/Schedule.java b/src/main/java/org/mtransit/android/commons/data/Schedule.java index f6e8f3ac..49029ce2 100644 --- a/src/main/java/org/mtransit/android/commons/data/Schedule.java +++ b/src/main/java/org/mtransit/android/commons/data/Schedule.java @@ -17,7 +17,6 @@ import org.mtransit.android.commons.R; import org.mtransit.android.commons.StringUtils; import org.mtransit.android.commons.TimeUtils; -import org.mtransit.android.commons.provider.status.StatusProviderContract; import org.mtransit.commons.CollectionUtils; import java.util.ArrayList; @@ -900,221 +899,4 @@ public static JSONObject toJSON(@NonNull Timestamp timestamp) { } } } - - public static class ScheduleStatusFilter extends StatusProviderContract.Filter { - - private static final String LOG_TAG = ScheduleStatusFilter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - @SuppressWarnings("unused") // main app - public static final int DATA_REQUEST_MONTHS = 62; - @SuppressWarnings("unused") - public static final int DATA_REQUEST_YEAR = 365; - - private static final long MIN_USEFUL_DURATION_COVERED_IN_MS_DEFAULT = TimeUnit.DAYS.toMillis(1L); - private static final int MIN_USEFUL_RESULTS_DEFAULT = 10; - public static final int MAX_DATA_REQUESTS_DEFAULT = 7 * 7; // 7 weeks - private static final long LOOK_BEHIND_IN_MS_DEFAULT = TimeUnit.MILLISECONDS.toMillis(0L); - - @NonNull - private final RouteDirectionStop routeDirectionStop; - @Nullable - private Long lookBehindInMs = null; - @Nullable - private Long minUsefulDurationCoveredInMs = null; - @Nullable - private Integer minUsefulResults = null; - @Nullable - private Integer maxDataRequests = null; - @Nullable - private Boolean includeCancelledTimestamps = null; - - public ScheduleStatusFilter(@NonNull RouteDirectionStop rds) { - super(POI.ITEM_STATUS_TYPE_SCHEDULE, rds.getUUID()); - this.routeDirectionStop = rds; - } - - @NonNull - public RouteDirectionStop getRouteDirectionStop() { - return routeDirectionStop; - } - - @NonNull - public String getTargetAuthority() { - return this.routeDirectionStop.getAuthority(); - } - - public long getRouteId() { - return this.routeDirectionStop.getRoute().getId(); - } - - public long getDirectionId() { - return this.routeDirectionStop.getDirection().getId(); - } - - public long getLookBehindInMsOrDefault() { - return lookBehindInMs == null ? LOOK_BEHIND_IN_MS_DEFAULT : lookBehindInMs; - } - - public void setLookBehindInMs(@Nullable Long lookBehindInMs) { - this.lookBehindInMs = lookBehindInMs; - } - - public long getTimestampOrDefault() { - return getNewDefaultTimestamp(); - } - - public long getMinUsefulDurationCoveredInMsOrDefault() { - return this.minUsefulDurationCoveredInMs == null ? MIN_USEFUL_DURATION_COVERED_IN_MS_DEFAULT : this.minUsefulDurationCoveredInMs; - } - - @SuppressWarnings("unused") - public void setMinUsefulDurationCoveredInMs(@Nullable Long minUsefulDurationCoveredInMs) { - this.minUsefulDurationCoveredInMs = minUsefulDurationCoveredInMs; - } - - public int getMinUsefulResultsOrDefault() { - return minUsefulResults == null ? MIN_USEFUL_RESULTS_DEFAULT : minUsefulResults; - } - - @SuppressWarnings("unused") - public void setMinUsefulResults(@Nullable Integer minUsefulResults) { - this.minUsefulResults = minUsefulResults; - } - - public int getMaxDataRequestsOrDefault() { - return maxDataRequests == null ? MAX_DATA_REQUESTS_DEFAULT : maxDataRequests; - } - - public void setMaxDataRequests(@Nullable Integer maxDataRequests) { - this.maxDataRequests = maxDataRequests; - } - - public boolean isIncludeCancelledTimestampsOrDefault() { - return Boolean.TRUE.equals(this.includeCancelledTimestamps); - } - - @SuppressWarnings("unused") // main app - public void setIncludeCancelledTimestamps(@Nullable Boolean includeCancelledTimestamps) { - this.includeCancelledTimestamps = includeCancelledTimestamps; - } - - private static long getNewDefaultTimestamp() { - return TimeUtils.currentTimeToTheMinuteMillis(); - } - - @Nullable - @Override - public StatusProviderContract.Filter fromJSONStringStatic(@Nullable String jsonString) { - return fromJSONString(jsonString); - } - - @Nullable - public static StatusProviderContract.Filter fromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? null : fromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return null; - } - } - - private static final String JSON_MIN_USEFUL_DURATION_COVERED_IN_MS = "minUsefulDurationCoveredInMs"; - private static final String JSON_MIN_USEFUL_RESULTS = "minUsefulResults"; - private static final String JSON_MAX_DATA_REQUESTS = "maxDataRequests"; - private static final String JSON_ROUTE_DIRECTION_STOP = "routeTripStop"; // do not change to avoid breaking compat w/ old modules - private static final String JSON_LOOK_BEHIND_IN_MS = "lookBehindInMs"; - private static final String JSON_INCLUDE_CANCELLED_TIMESTAMPS = "includeCancelledTimestamps"; - - @Nullable - public static StatusProviderContract.Filter fromJSON(@NonNull JSONObject json) { - try { - final RouteDirectionStop routeDirectionStop = RouteDirectionStop.fromJSONStatic(json.getJSONObject(JSON_ROUTE_DIRECTION_STOP)); - if (routeDirectionStop == null) { - return null; - } - final ScheduleStatusFilter scheduleStatusFilter = new ScheduleStatusFilter(routeDirectionStop); - StatusProviderContract.Filter.fromJSON(scheduleStatusFilter, json); - scheduleStatusFilter.lookBehindInMs = json.has(JSON_LOOK_BEHIND_IN_MS) ? json.getLong(JSON_LOOK_BEHIND_IN_MS) : null; - scheduleStatusFilter.minUsefulDurationCoveredInMs = - json.has(JSON_MIN_USEFUL_DURATION_COVERED_IN_MS) ? json.getLong(JSON_MIN_USEFUL_DURATION_COVERED_IN_MS) : null; - scheduleStatusFilter.minUsefulResults = json.has(JSON_MIN_USEFUL_RESULTS) ? json.getInt(JSON_MIN_USEFUL_RESULTS) : null; - scheduleStatusFilter.maxDataRequests = json.has(JSON_MAX_DATA_REQUESTS) ? json.getInt(JSON_MAX_DATA_REQUESTS) : null; - scheduleStatusFilter.includeCancelledTimestamps = - json.has(JSON_INCLUDE_CANCELLED_TIMESTAMPS) ? json.optBoolean(JSON_INCLUDE_CANCELLED_TIMESTAMPS, false) : null; - return scheduleStatusFilter; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); - return null; - } - } - - @Nullable - @Override - public String toJSONString() { - return toJSONString(this); - } - - @Nullable - @Override - public String toJSONStringStatic(@NonNull StatusProviderContract.Filter statusFilter) { - return toJSONString(statusFilter); - } - - @Nullable - static String toJSONString(@NonNull StatusProviderContract.Filter statusFilter) { - final JSONObject json = toJSON(statusFilter); - return json == null ? null : json.toString(); - } - - @Nullable - public static JSONObject toJSON(@NonNull StatusProviderContract.Filter statusFilter) { - try { - final JSONObject json = new JSONObject(); - StatusProviderContract.Filter.toJSON(statusFilter, json); - if (statusFilter instanceof ScheduleStatusFilter) { - final ScheduleStatusFilter scheduleFilter = (ScheduleStatusFilter) statusFilter; - json.put(JSON_ROUTE_DIRECTION_STOP, scheduleFilter.routeDirectionStop.toJSON()); - if (scheduleFilter.lookBehindInMs != null) { - json.put(JSON_LOOK_BEHIND_IN_MS, scheduleFilter.lookBehindInMs); - } - if (scheduleFilter.minUsefulDurationCoveredInMs != null) { - json.put(JSON_MIN_USEFUL_DURATION_COVERED_IN_MS, scheduleFilter.minUsefulDurationCoveredInMs); - } - if (scheduleFilter.minUsefulResults != null) { - json.put(JSON_MIN_USEFUL_RESULTS, scheduleFilter.minUsefulResults); - } - if (scheduleFilter.maxDataRequests != null) { - json.put(JSON_MAX_DATA_REQUESTS, scheduleFilter.maxDataRequests); - } - if (scheduleFilter.includeCancelledTimestamps != null) { - json.put(JSON_INCLUDE_CANCELLED_TIMESTAMPS, scheduleFilter.includeCancelledTimestamps); - } - } - return json; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", statusFilter); - return null; - } - } - - @NonNull - @Override - public String toString() { - return ScheduleStatusFilter.class.getSimpleName() + "{" + - super.toString() + - ", rds=" + routeDirectionStop + - ", lookBehindInMs=" + lookBehindInMs + - ", minUsefulDurationCoveredInMs=" + minUsefulDurationCoveredInMs + - ", minUsefulResults=" + minUsefulResults + - ", maxDataRequests=" + maxDataRequests + - ", includeCancelledTimestamps=" + includeCancelledTimestamps + - '}'; - } - } } diff --git a/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt b/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt new file mode 100644 index 00000000..a5f97d48 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt @@ -0,0 +1,165 @@ +package org.mtransit.android.commons.data + +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.TimeUtils +import org.mtransit.android.commons.optBoolean +import org.mtransit.android.commons.optInt +import org.mtransit.android.commons.optLong +import org.mtransit.android.commons.provider.status.StatusProviderContract +import java.util.concurrent.TimeUnit + +data class ScheduleStatusFilter( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, + val routeDirectionStop: RouteDirectionStop, + private val lookBehindInMs: Long? = null, + private val minUsefulDurationCoveredInMs: Long? = null, + private val minUsefulResults: Int? = null, + private val maxDataRequests: Int? = null, + private val includeCancelledTimestamps: Boolean? = null, +) : StatusProviderContract.Filter(POI.ITEM_STATUS_TYPE_SCHEDULE, targetUUID = routeDirectionStop.uuid) { + + companion object { + private val LOG_TAG: String = ScheduleStatusFilter::class.java.getSimpleName() + + @Suppress("unused") // main app + const val DATA_REQUEST_MONTHS = 62 + + @Suppress("unused") + const val DATA_REQUEST_YEAR = 365 + + private val MIN_USEFUL_DURATION_COVERED_IN_MS_DEFAULT = TimeUnit.DAYS.toMillis(1L) + private const val MIN_USEFUL_RESULTS_DEFAULT = 10 + + const val MAX_DATA_REQUESTS_DEFAULT = 7 * 7 // 7 weeks + private val LOOK_BEHIND_IN_MS_DEFAULT = TimeUnit.MILLISECONDS.toMillis(0L) + + private val newDefaultTimestamp: Long get() = TimeUtils.currentTimeToTheMinuteMillis() + + @JvmStatic + fun from( + routeDirectionStop: RouteDirectionStop, + lookBehindInMs: Long?, + maxDataRequests: Int?, + includeCancelledTimestamps: Boolean?, + ) = ScheduleStatusFilter( + routeDirectionStop = routeDirectionStop, + lookBehindInMs = lookBehindInMs, + maxDataRequests = maxDataRequests, + includeCancelledTimestamps = includeCancelledTimestamps, + ) + + @JvmStatic + fun fromJSONString(jsonString: String?): StatusProviderContract.Filter? { + try { + return jsonString?.let { fromJSON(JSONObject(it)) } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '$jsonString'") + return null + } + } + + private const val JSON_MIN_USEFUL_DURATION_COVERED_IN_MS = "minUsefulDurationCoveredInMs" + private const val JSON_MIN_USEFUL_RESULTS = "minUsefulResults" + private const val JSON_MAX_DATA_REQUESTS = "maxDataRequests" + private const val JSON_ROUTE_DIRECTION_STOP = "routeTripStop" // do not change to avoid breaking compat w/ old modules + private const val JSON_LOOK_BEHIND_IN_MS = "lookBehindInMs" + private const val JSON_INCLUDE_CANCELLED_TIMESTAMPS = "includeCancelledTimestamps" + + fun fromJSON(json: JSONObject): StatusProviderContract.Filter? { + try { + val routeDirectionStop = RouteDirectionStop.fromJSONStatic(json.getJSONObject(JSON_ROUTE_DIRECTION_STOP)) ?: return null + return ScheduleStatusFilter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + routeDirectionStop = routeDirectionStop, + lookBehindInMs = json.optLong(JSON_LOOK_BEHIND_IN_MS, null), + minUsefulDurationCoveredInMs = json.optLong(JSON_MIN_USEFUL_DURATION_COVERED_IN_MS, null), + minUsefulResults = json.optInt(JSON_MIN_USEFUL_RESULTS, null), + maxDataRequests = json.optInt(JSON_MAX_DATA_REQUESTS, null), + includeCancelledTimestamps = json.optBoolean(JSON_INCLUDE_CANCELLED_TIMESTAMPS, null) + ) + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '$json'") + return null + } + } + + fun toJSONString(statusFilter: StatusProviderContract.Filter) = toJSON(statusFilter)?.toString() + + fun toJSON(statusFilter: StatusProviderContract.Filter): JSONObject? { + try { + val json = JSONObject() + toJSON(statusFilter, json) + if (statusFilter is ScheduleStatusFilter) { + json.put(JSON_ROUTE_DIRECTION_STOP, statusFilter.routeDirectionStop.toJSON()) + if (statusFilter.lookBehindInMs != null) { + json.put(JSON_LOOK_BEHIND_IN_MS, statusFilter.lookBehindInMs) + } + if (statusFilter.minUsefulDurationCoveredInMs != null) { + json.put(JSON_MIN_USEFUL_DURATION_COVERED_IN_MS, statusFilter.minUsefulDurationCoveredInMs) + } + if (statusFilter.minUsefulResults != null) { + json.put(JSON_MIN_USEFUL_RESULTS, statusFilter.minUsefulResults) + } + if (statusFilter.maxDataRequests != null) { + json.put(JSON_MAX_DATA_REQUESTS, statusFilter.maxDataRequests) + } + if (statusFilter.includeCancelledTimestamps != null) { + json.put(JSON_INCLUDE_CANCELLED_TIMESTAMPS, statusFilter.includeCancelledTimestamps) + } + } + return json + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while making JSON object '$statusFilter'") + return null + } + } + } + + override fun getLogTag() = LOG_TAG + + override fun copyWith(providedEncryptKeysMap: Map?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) + + val targetAuthority: String get() = this.routeDirectionStop.authority + + val routeId: Long get() = this.routeDirectionStop.route.id + + val directionId: Long get() = this.routeDirectionStop.direction.id + + val lookBehindInMsOrDefault: Long get() = lookBehindInMs ?: LOOK_BEHIND_IN_MS_DEFAULT + + val timestampOrDefault: Long get() = newDefaultTimestamp + + val minUsefulDurationCoveredInMsOrDefault: Long get() = minUsefulDurationCoveredInMs ?: MIN_USEFUL_DURATION_COVERED_IN_MS_DEFAULT + + val minUsefulResultsOrDefault: Int get() = minUsefulResults ?: MIN_USEFUL_RESULTS_DEFAULT + + val maxDataRequestsOrDefault: Int get() = maxDataRequests ?: MAX_DATA_REQUESTS_DEFAULT + + val isIncludeCancelledTimestampsOrDefault: Boolean get() = this.includeCancelledTimestamps == true + + override fun fromJSONStringStatic(jsonString: String?) = fromJSONString(jsonString) + + override fun toJSONString() = toJSONString(this) + + override fun toJSONStringStatic(statusFilter: StatusProviderContract.Filter) = toJSONString(statusFilter) + + override fun toString(): String { + return ScheduleStatusFilter::class.java.getSimpleName() + "{" + + super.toString() + + ", rds=" + routeDirectionStop + + ", lookBehindInMs=" + lookBehindInMs + + ", minUsefulDurationCoveredInMs=" + minUsefulDurationCoveredInMs + + ", minUsefulResults=" + minUsefulResults + + ", maxDataRequests=" + maxDataRequests + + ", includeCancelledTimestamps=" + includeCancelledTimestamps + + '}' + } +} diff --git a/src/main/java/org/mtransit/android/commons/data/ServiceUpdateKtx.kt b/src/main/java/org/mtransit/android/commons/data/ServiceUpdateKtx.kt index f61dfe08..e984af5f 100644 --- a/src/main/java/org/mtransit/android/commons/data/ServiceUpdateKtx.kt +++ b/src/main/java/org/mtransit/android/commons/data/ServiceUpdateKtx.kt @@ -26,12 +26,12 @@ fun ServiceUpdateProviderContract.makeServiceUpdateNone(targetUUID: String, sour makeServiceUpdate( targetUUID = targetUUID, lastUpdateMs = TimeUtils.currentTimeMillis(), - maxValidityMs = getServiceUpdateMaxValidityInMs(), + maxValidityMs = serviceUpdateMaxValidityInMs, text = StringUtils.EMPTY, severity = ServiceUpdate.SEVERITY_NONE, sourceId = sourceId, sourceLabel = StringUtils.EMPTY, - language = getServiceUpdateLanguage(), + language = serviceUpdateLanguage, ) fun makeServiceUpdate( diff --git a/src/main/java/org/mtransit/android/commons/provider/CleverDevicesProvider.java b/src/main/java/org/mtransit/android/commons/provider/CleverDevicesProvider.java index bcb6e09b..c9d3f31f 100644 --- a/src/main/java/org/mtransit/android/commons/provider/CleverDevicesProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/CleverDevicesProvider.java @@ -27,6 +27,7 @@ import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; import org.mtransit.android.commons.data.Schedule.Timestamp; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.helpers.MTDefaultHandler; import org.mtransit.android.commons.provider.agency.AgencyUtils; import org.mtransit.android.commons.provider.common.MTContentProvider; @@ -210,11 +211,11 @@ public void cacheStatus(@NonNull POIStatus newStatusToCache) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getCachedStatus() > Can't find new schedule w/o schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); if (TextUtils.isEmpty(rds.getStop().getCode())) { return null; @@ -262,11 +263,11 @@ public int getStatusType() { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule w/o schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); if (TextUtils.isEmpty(rds.getStop().getCode())) { return null; diff --git a/src/main/java/org/mtransit/android/commons/provider/GBFSProvider.java b/src/main/java/org/mtransit/android/commons/provider/GBFSProvider.java index c215aeb7..958957ff 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GBFSProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GBFSProvider.java @@ -15,7 +15,7 @@ import org.mtransit.android.commons.NetworkUtils; import org.mtransit.android.commons.TimeUtils; import org.mtransit.android.commons.data.Area; -import org.mtransit.android.commons.data.AvailabilityPercent; +import org.mtransit.android.commons.data.AvailabilityPercentStatusFilter; import org.mtransit.android.commons.data.BikeStationAvailabilityPercent; import org.mtransit.android.commons.data.DefaultPOI; import org.mtransit.android.commons.data.POI; @@ -74,7 +74,7 @@ public long getLastUpdateInMs() { // POI @Nullable @Override - public POIStatus getNewBikeStationStatus(@NonNull AvailabilityPercent.AvailabilityPercentStatusFilter statusFilter) { + public POIStatus getNewBikeStationStatus(@NonNull AvailabilityPercentStatusFilter statusFilter) { updateBikeStationStatusDataIfRequired(statusFilter); return getCachedStatus(statusFilter); } @@ -85,8 +85,8 @@ public void updateBikeStationDataIfRequired() { final long lastUpdateInMs = getStorage(context).getLastUpdateInMs(); // POI final long nowInMs = TimeUtils.currentTimeMillis(); // MAX VALIDITY (too old to display?) - if (lastUpdateInMs + getPOIValidityInMs() < nowInMs) { // try to update - if (lastUpdateInMs + getPOIMaxValidityInMs() < nowInMs) { // too old to display + if (lastUpdateInMs + getPoiValidityInMs() < nowInMs) { // try to update + if (lastUpdateInMs + getPoiMaxValidityInMs() < nowInMs) { // too old to display deleteAllBikeStationData(); updateBikeStationDataFromWWW(context, lastUpdateInMs); return; diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSProvider.java b/src/main/java/org/mtransit/android/commons/provider/GTFSProvider.java index 39cf9a4c..9b68d3eb 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSProvider.java @@ -401,12 +401,12 @@ public ArrayMap getSearchSuggestProjectionMap() { } @Override - public long getPOIMaxValidityInMs() { + public long getPoiMaxValidityInMs() { return GTFSPOIProvider.getPOIMaxValidityInMs(this); } @Override - public long getPOIValidityInMs() { + public long getPoiValidityInMs() { return GTFSPOIProvider.getPOIValidityInMs(this); } @@ -424,13 +424,13 @@ public Cursor getPOIFromDB(@Nullable POIProviderContract.Filter poiFilter) { @NonNull @Override - public String[] getPOIProjection() { + public String[] getPoiProjection() { return GTFSPOIProvider.getPOIProjection(this); } @NonNull @Override - public ArrayMap getPOIProjectionMap() { + public ArrayMap getPoiProjectionMap() { return GTFSPOIProvider.getPOIProjectionMap(this); } diff --git a/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java index 4c092dcd..a81a7219 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSProviderContract.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import org.mtransit.android.commons.ArrayUtils; -import org.mtransit.android.commons.provider.poi.POIProvider; +import org.mtransit.android.commons.provider.poi.POIProviderContract; import org.mtransit.commons.FeatureFlags; import java.util.ArrayList; @@ -79,7 +79,7 @@ static String[] makePROJECTION_ROUTE() { DirectionColumns.T_DIRECTION_K_ROUTE_ID }; - String[] PROJECTION_RDS_POI = ArrayUtils.addAllNonNull(POIProvider.PROJECTION_POI, PROJECTION_ROUTE_DIRECTION_STOP); + String[] PROJECTION_RDS_POI = ArrayUtils.addAllNonNull(POIProviderContract.getPROJECTION_POI(), PROJECTION_ROUTE_DIRECTION_STOP); String[] PROJECTION_TRIP = new String[] { TripColumns.T_TRIP_K_TRIP_ID, 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 909cee04..d5bf20c6 100644 --- a/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/GTFSRealTimeProvider.java @@ -1488,7 +1488,7 @@ public boolean purgeUselessCachedServiceUpdates() { } @Override - public boolean deleteCachedServiceUpdate(@NonNull Integer serviceUpdateId) { + public boolean deleteCachedServiceUpdate(int serviceUpdateId) { return ServiceUpdateProvider.deleteCachedServiceUpdate(this, serviceUpdateId); } diff --git a/src/main/java/org/mtransit/android/commons/provider/InstagramNewsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/InstagramNewsProvider.kt index b8fa0036..48ddf1b4 100644 --- a/src/main/java/org/mtransit/android/commons/provider/InstagramNewsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/InstagramNewsProvider.kt @@ -30,6 +30,7 @@ import org.mtransit.android.commons.data.News import org.mtransit.android.commons.provider.InstagramNewsProvider.InstagramApi.JEdgeOwnerToTimelineMediaNode import org.mtransit.android.commons.provider.InstagramNewsProvider.InstagramApi.JProfileUser import org.mtransit.android.commons.provider.agency.AgencyUtils +import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.news.NewsProvider import org.mtransit.android.commons.provider.news.NewsProviderContract import retrofit2.Call @@ -57,7 +58,7 @@ class InstagramNewsProvider : NewsProvider() { */ private const val PREF_KEY_AGENCY_LAST_UPDATE_LANG = InstagramNewsDbHelper.PREF_KEY_AGENCY_LAST_UPDATE_LANG - private val NEWS_MAX_VALIDITY_IN_MS = MAX_CACHE_VALIDITY_MS + private val NEWS_MAX_VALIDITY_IN_MS = ProviderContract.MAX_CACHE_VALIDITY_MS private val NEWS_VALIDITY_IN_MS = TimeUnit.DAYS.toMillis(1L) private val NEWS_VALIDITY_IN_FOCUS_IN_MS = TimeUnit.HOURS.toMillis(1L) private val NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS = TimeUnit.MINUTES.toMillis(30L) @@ -157,7 +158,7 @@ class InstagramNewsProvider : NewsProvider() { override fun getLogTag() = LOG_TAG - override fun getURI_MATCHER() = _uriMatcher + override val URI_MATCHER: UriMatcher get() = _uriMatcher private var _dbHelper: InstagramNewsDbHelper? = null private var _currentDbVersion: Int = -1 @@ -199,13 +200,13 @@ class InstagramNewsProvider : NewsProvider() { // override fun getDBHelper() = getDBHelper(ContentProviderCompat.requireContext(this)) - override fun getMinDurationBetweenNewsRefreshInMs(inFocus: Boolean) = if (inFocus) { + override fun getMinDurationBetweenNewsRefreshInMs(inFocusOrDefault: Boolean) = if (inFocusOrDefault) { NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_FOCUS_IN_MS } else NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS - override fun getNewsMaxValidityInMs() = NEWS_MAX_VALIDITY_IN_MS + override val newsMaxValidityInMs: Long get() = NEWS_MAX_VALIDITY_IN_MS - override fun getNewsValidityInMs(inFocus: Boolean) = if (inFocus) { + override fun getNewsValidityInMs(inFocusOrDefault: Boolean) = if (inFocusOrDefault) { NEWS_VALIDITY_IN_FOCUS_IN_MS } else NEWS_VALIDITY_IN_MS @@ -213,8 +214,8 @@ class InstagramNewsProvider : NewsProvider() { return purgeUselessCachedNews(this) } - override fun deleteCachedNews(newsId: Int?): Boolean { - return deleteCachedNews(this, newsId) + override fun deleteCachedNews(id: Int?): Boolean { + return deleteCachedNews(this, id) } private fun deleteAllAgencyNewsData(): Int { @@ -235,9 +236,9 @@ class InstagramNewsProvider : NewsProvider() { return affectedRows } - override fun getAuthority() = _authority + override val authority: String get() = _authority - override fun getAuthorityUri() = _authorityUri + override val authorityUri: Uri get() = _authorityUri override fun cacheNews(newNews: ArrayList) { cacheNewsS(this, newNews) @@ -626,7 +627,7 @@ class InstagramNewsProvider : NewsProvider() { ) } - override fun getNewsLanguages() = _languages + override val newsLanguages: Collection get() = _languages class InstagramNewsDbHelper( val context: Context, diff --git a/src/main/java/org/mtransit/android/commons/provider/NextBusProvider.java b/src/main/java/org/mtransit/android/commons/provider/NextBusProvider.java index 220e18a2..0e93c4d3 100644 --- a/src/main/java/org/mtransit/android/commons/provider/NextBusProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/NextBusProvider.java @@ -32,6 +32,7 @@ import org.mtransit.android.commons.data.RouteDirection; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.data.ServiceUpdate; import org.mtransit.android.commons.data.ServiceUpdateKtxKt; import org.mtransit.android.commons.data.ServiceUpdates; @@ -662,8 +663,9 @@ private ServiceUpdates getCachedServiceUpdates(@NonNull Context context, @NonNul return cachedServiceUpdates; } - private void enhanceRDServiceUpdateForStop(@Nullable ServiceUpdates serviceUpdates, - @NonNull Map targetUUIDs // different UUID from provider target UUID + private void enhanceRDServiceUpdateForStop( + @Nullable ServiceUpdates serviceUpdates, + @NonNull Map targetUUIDs // different UUID from provider target UUID ) { try { if (serviceUpdates != null) { @@ -808,7 +810,7 @@ public boolean purgeUselessCachedServiceUpdates() { } @Override - public boolean deleteCachedServiceUpdate(@NonNull Integer serviceUpdateId) { + public boolean deleteCachedServiceUpdate(int serviceUpdateId) { return ServiceUpdateProvider.deleteCachedServiceUpdate(this, serviceUpdateId); } @@ -1062,11 +1064,11 @@ public void cacheStatus(@NonNull POIStatus newStatusToCache) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule w/o schedule filter!"); return null; } - final Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + final ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; final RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); final String targetUUID = getAgencyRouteStopTagTargetUUID(requireContextCompat(), rds); final POIStatus cachedStatus = StatusProvider.getCachedStatusS(this, targetUUID); @@ -1106,11 +1108,11 @@ public int getStatusType() { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule w/o schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); loadPredictionsFromWWW(requireContextCompat(), rds); return getCachedStatus(statusFilter); @@ -1661,11 +1663,16 @@ public String getLogTag() { private final NextBusProvider provider; - NextBusMessagesDataHandler(NextBusProvider provider, @NonNull String sourceLabel, long newLastUpdateInMs, - String agencyTag, - long serviceUpdateMaxValidityInMs, - String textLanguageCode, String textSecondaryLanguageCode, - String textBoldWordsRegex, String textSecondaryBoldWordsRegex + NextBusMessagesDataHandler( + NextBusProvider provider, + @NonNull String sourceLabel, + long newLastUpdateInMs, + String agencyTag, + long serviceUpdateMaxValidityInMs, + String textLanguageCode, + String textSecondaryLanguageCode, + String textBoldWordsRegex, + String textSecondaryBoldWordsRegex ) { this.provider = provider; this.sourceLabel = sourceLabel; diff --git a/src/main/java/org/mtransit/android/commons/provider/OneBusAwayProvider.java b/src/main/java/org/mtransit/android/commons/provider/OneBusAwayProvider.java index 2e61c56a..054745a1 100644 --- a/src/main/java/org/mtransit/android/commons/provider/OneBusAwayProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/OneBusAwayProvider.java @@ -31,6 +31,7 @@ import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.provider.agency.AgencyUtils; import org.mtransit.android.commons.provider.common.MTContentProvider; import org.mtransit.android.commons.provider.common.MTSQLiteOpenHelper; @@ -252,11 +253,11 @@ public void cacheStatus(@NonNull POIStatus newStatusToCache) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); String targetUUID = getAgencyRouteStopTagTargetUUID(rds); POIStatus cachedStatus = StatusProvider.getCachedStatusS(this, targetUUID); @@ -307,11 +308,11 @@ public int getStatusType() { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); loadPredictionsFromWWW(requireContextCompat(), rds); return getCachedStatus(statusFilter); @@ -485,6 +486,7 @@ protected String cleanDirectionHeadsign(@NonNull Context context, @NonNull Strin final String rdsDirectionHeading = rds.getDirection().getHeading(context); final String routeLongName = rds.getRoute().getLongName(); tripHeadsign = CleanUtils.removeStrings(tripHeadsign, rdsDirectionHeading, routeLongName); + //noinspection deprecation tripHeadsign = CleanUtils.cleanLabel(tripHeadsign); return tripHeadsign; } catch (Exception e) { diff --git a/src/main/java/org/mtransit/android/commons/provider/RSSNewsProvider.java b/src/main/java/org/mtransit/android/commons/provider/RSSNewsProvider.java index 9823c78b..78db294f 100644 --- a/src/main/java/org/mtransit/android/commons/provider/RSSNewsProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/RSSNewsProvider.java @@ -30,6 +30,7 @@ import org.mtransit.android.commons.data.News; import org.mtransit.android.commons.helpers.MTDefaultHandler; import org.mtransit.android.commons.provider.agency.AgencyUtils; +import org.mtransit.android.commons.provider.common.ProviderContract; import org.mtransit.android.commons.provider.news.NewsProvider; import org.mtransit.android.commons.provider.news.NewsTextFormatter; import org.mtransit.android.commons.provider.news.rss.RssNewProviderUtils; @@ -308,7 +309,7 @@ public SQLiteDatabase getWriteDB() { return getDBHelper().getWritableDatabase(); } - private static final long NEWS_MAX_VALIDITY_IN_MS = MAX_CACHE_VALIDITY_MS; + private static final long NEWS_MAX_VALIDITY_IN_MS = ProviderContract.getMAX_CACHE_VALIDITY_MS(); private static final long NEWS_VALIDITY_IN_MS = TimeUnit.DAYS.toMillis(1L); private static final long NEWS_VALIDITY_IN_FOCUS_IN_MS = TimeUnit.HOURS.toMillis(1L); private static final long NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS = TimeUnit.MINUTES.toMillis(30L); diff --git a/src/main/java/org/mtransit/android/commons/provider/RTCQuebecProvider.java b/src/main/java/org/mtransit/android/commons/provider/RTCQuebecProvider.java index 7cfd552c..5f451c28 100644 --- a/src/main/java/org/mtransit/android/commons/provider/RTCQuebecProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/RTCQuebecProvider.java @@ -36,6 +36,7 @@ import org.mtransit.android.commons.data.RouteDirection; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.data.ServiceUpdate; import org.mtransit.android.commons.data.ServiceUpdateKtxKt; import org.mtransit.android.commons.data.ServiceUpdates; @@ -463,11 +464,11 @@ private int findStopSeverity(String originalHtml, @NonNull Stop stop) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); if (TextUtils.isEmpty(rds.getStop().getCode()) || rds.getDirection().getId() < 0L @@ -526,7 +527,7 @@ public boolean purgeUselessCachedStatuses() { } @Override - public boolean deleteCachedServiceUpdate(@NonNull Integer serviceUpdateId) { + public boolean deleteCachedServiceUpdate(int serviceUpdateId) { return ServiceUpdateProvider.deleteCachedServiceUpdate(this, serviceUpdateId); } @@ -710,11 +711,11 @@ private ServiceUpdates loadAgencyServiceUpdateDataFromWWW(@NonNull Context conte @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); if (TextUtils.isEmpty(rds.getStop().getCode()) || TextUtils.isEmpty(rds.getRoute().getShortName())) { diff --git a/src/main/java/org/mtransit/android/commons/provider/ReginaTransitProvider.java b/src/main/java/org/mtransit/android/commons/provider/ReginaTransitProvider.java index 813451c8..e50559fc 100644 --- a/src/main/java/org/mtransit/android/commons/provider/ReginaTransitProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/ReginaTransitProvider.java @@ -32,6 +32,7 @@ import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.provider.common.MTContentProvider; import org.mtransit.android.commons.provider.common.MTSQLiteOpenHelper; import org.mtransit.android.commons.provider.status.StatusProvider; @@ -151,11 +152,11 @@ public void cacheStatus(@NonNull POIStatus newStatusToCache) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); POIStatus status = StatusProvider.getCachedStatusS(this, rds.getUUID()); if (status != null) { @@ -191,11 +192,11 @@ public int getStatusType() { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); loadRealTimeStatusFromWWW(rds); return getCachedStatus(statusFilter); diff --git a/src/main/java/org/mtransit/android/commons/provider/StmInfoApiProvider.java b/src/main/java/org/mtransit/android/commons/provider/StmInfoApiProvider.java index 39350773..cfbbd59d 100644 --- a/src/main/java/org/mtransit/android/commons/provider/StmInfoApiProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/StmInfoApiProvider.java @@ -46,6 +46,7 @@ import org.mtransit.android.commons.data.RouteDirection; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.data.ServiceUpdate; import org.mtransit.android.commons.data.ServiceUpdateKtxKt; import org.mtransit.android.commons.data.ServiceUpdates; @@ -242,11 +243,11 @@ public void cacheServiceUpdates(@Nullable ServiceUpdates newServiceUpdates) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getCachedStatus() > Can't find new schedule without schedule filter!"); return null; } - final Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + final ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; final RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); if (rds.getStop().getCode().isEmpty() // || rds.getDirection().getHeadsignValue().isEmpty() // @@ -534,7 +535,7 @@ public boolean deleteCachedStatus(int cachedStatusId) { } @Override - public boolean deleteCachedServiceUpdate(@NonNull Integer serviceUpdateId) { + public boolean deleteCachedServiceUpdate(int serviceUpdateId) { return ServiceUpdateProvider.deleteCachedServiceUpdate(this, serviceUpdateId); } @@ -563,11 +564,11 @@ public int getStatusType() { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - final Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + final ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; final RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); if (rds.getStop().getCode().isEmpty() || rds.getRoute().getShortName().isEmpty()) { diff --git a/src/main/java/org/mtransit/android/commons/provider/TwitterNewsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/TwitterNewsProvider.kt index 62b316b3..4e1b0922 100644 --- a/src/main/java/org/mtransit/android/commons/provider/TwitterNewsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/TwitterNewsProvider.kt @@ -25,17 +25,18 @@ import org.mtransit.android.commons.TimeUtils import org.mtransit.android.commons.UriUtils import org.mtransit.android.commons.data.News import org.mtransit.android.commons.provider.agency.AgencyUtils +import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.news.NewsProvider import org.mtransit.android.commons.provider.news.NewsProviderContract import org.mtransit.android.commons.provider.news.NewsTextFormatter -import org.mtransit.android.commons.provider.news.twitter.model.Tweet -import org.mtransit.android.commons.provider.news.twitter.model.TweetMediaType -import org.mtransit.android.commons.provider.news.twitter.TwitterV2Api import org.mtransit.android.commons.provider.news.twitter.TwitterDateAdapter import org.mtransit.android.commons.provider.news.twitter.TwitterNewsDbHelper import org.mtransit.android.commons.provider.news.twitter.TwitterStorage -import org.mtransit.android.commons.provider.news.twitter.model.TweetsResponse.TweetIncludes +import org.mtransit.android.commons.provider.news.twitter.TwitterV2Api +import org.mtransit.android.commons.provider.news.twitter.model.Tweet import org.mtransit.android.commons.provider.news.twitter.model.TweetMedia.TweetMediaVariant +import org.mtransit.android.commons.provider.news.twitter.model.TweetMediaType +import org.mtransit.android.commons.provider.news.twitter.model.TweetsResponse.TweetIncludes import retrofit2.create import java.io.IOException import java.util.Date @@ -58,7 +59,7 @@ class TwitterNewsProvider : NewsProvider() { // https://docs.x.com/x-api/fundamentals/rate-limits (Basic) // - [GET /2/users/by/username/:username] 500 requests / 24 hours PER APP // - [GET /2/tweets] 15 requests / 15 mins PER APP - private val NEWS_MAX_VALIDITY_IN_MS = MAX_CACHE_VALIDITY_MS + private val NEWS_MAX_VALIDITY_IN_MS = ProviderContract.MAX_CACHE_VALIDITY_MS private val NEWS_VALIDITY_IN_MS = TimeUnit.HOURS.toMillis(24L) * VALIDITY_EXPANSIVE_API_FACTOR private val NEWS_VALIDITY_IN_FOCUS_IN_MS = TimeUnit.HOURS.toMillis(1L) * VALIDITY_EXPANSIVE_API_FACTOR private val NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS = TimeUnit.MINUTES.toMillis(30L) * VALIDITY_EXPANSIVE_API_FACTOR @@ -234,7 +235,7 @@ class TwitterNewsProvider : NewsProvider() { override fun getLogTag() = LOG_TAG - override fun getURI_MATCHER() = _uriMatcher + override val URI_MATCHER: UriMatcher get() = _uriMatcher private var _dbHelper: TwitterNewsDbHelper? = null private var _currentDbVersion: Int = -1 @@ -276,14 +277,13 @@ class TwitterNewsProvider : NewsProvider() { fun getDBHelper() = getDBHelper(ContentProviderCompat.requireContext(this)) - override fun getMinDurationBetweenNewsRefreshInMs(inFocus: Boolean) = if (inFocus) { + override fun getMinDurationBetweenNewsRefreshInMs(inFocusOrDefault: Boolean) = if (inFocusOrDefault) { NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_FOCUS_IN_MS } else NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS - override fun getNewsMaxValidityInMs() = NEWS_MAX_VALIDITY_IN_MS - .takeUnless { FORCE_REFRESH } ?: 0L + override val newsMaxValidityInMs: Long get() = NEWS_MAX_VALIDITY_IN_MS.takeUnless { FORCE_REFRESH } ?: 0L - override fun getNewsValidityInMs(inFocus: Boolean) = if (inFocus) { + override fun getNewsValidityInMs(inFocusOrDefault: Boolean) = if (inFocusOrDefault) { NEWS_VALIDITY_IN_FOCUS_IN_MS } else NEWS_VALIDITY_IN_MS @@ -291,8 +291,8 @@ class TwitterNewsProvider : NewsProvider() { return purgeUselessCachedNews(this) } - override fun deleteCachedNews(newsId: Int?): Boolean { - return deleteCachedNews(this, newsId) + override fun deleteCachedNews(id: Int?): Boolean { + return deleteCachedNews(this, id) } private fun deleteAllAgencyNewsData(): Int { @@ -313,9 +313,9 @@ class TwitterNewsProvider : NewsProvider() { return affectedRows } - override fun getAuthority() = _authority + override val authority: String get() = _authority - override fun getAuthorityUri() = _authorityUri + override val authorityUri: Uri get() = _authorityUri override fun cacheNews(newNews: ArrayList) { cacheNewsS(this, newNews) @@ -599,6 +599,7 @@ class TwitterNewsProvider : NewsProvider() { return null } val author = includedExpansions?.users?.find { it.id == tweet.authorId } + @Suppress("SpellCheckingInspection") val authorUserName = author?.username ?: userName // "montransit" val authorName = author?.name ?: authorUserName // "MonTransit" @@ -615,7 +616,7 @@ class TwitterNewsProvider : NewsProvider() { } } val textHTML = buildString { - getHTMLText(tweet, includedExpansions, REMOVE_IMAGE_FROM_TEXT)?.let { append(it) } + getHTMLText(tweet, includedExpansions, NewsProviderContract.REMOVE_IMAGE_FROM_TEXT)?.let { append(it) } if (!TextUtils.isEmpty(link)) { if (isNotEmpty()) { append(HtmlUtils.BR).append(HtmlUtils.BR) @@ -792,5 +793,5 @@ class TwitterNewsProvider : NewsProvider() { return selected } - override fun getNewsLanguages() = _languages + override val newsLanguages: Collection get() = _languages } diff --git a/src/main/java/org/mtransit/android/commons/provider/WinnipegTransitProvider.java b/src/main/java/org/mtransit/android/commons/provider/WinnipegTransitProvider.java index 07810cd3..87b29d15 100644 --- a/src/main/java/org/mtransit/android/commons/provider/WinnipegTransitProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/WinnipegTransitProvider.java @@ -38,8 +38,10 @@ import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.provider.common.MTContentProvider; import org.mtransit.android.commons.provider.common.MTSQLiteOpenHelper; +import org.mtransit.android.commons.provider.common.ProviderContract; import org.mtransit.android.commons.provider.news.NewsProvider; import org.mtransit.android.commons.provider.news.NewsProviderContract; import org.mtransit.android.commons.provider.status.StatusProvider; @@ -222,11 +224,11 @@ public void cacheStatus(@NonNull POIStatus newStatusToCache) { @Nullable @Override public POIStatus getCachedStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); POIStatus cachedStatus = StatusProvider.getCachedStatusS(this, rds.getUUID()); if (cachedStatus != null) { @@ -264,11 +266,11 @@ public int getStatusType() { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without schedule filter!"); return null; } - Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; RouteDirectionStop rds = scheduleStatusFilter.getRouteDirectionStop(); this.providedApiKey = SecureStringUtils.dec(statusFilter.getProvidedEncryptKey(KeysIds.CA_WINNIPEG_TRANSIT_API_KEY)); loadRealTimeStatusFromWWW(requireContextCompat(), rds); @@ -561,7 +563,7 @@ private boolean isRealTime(@NonNull JSONObject jTimes) { */ private static final String PREF_KEY_AGENCY_NEWS_LAST_UPDATE_MS = WinnipegTransitDbHelper.PREF_KEY_AGENCY_NEWS_LAST_UPDATE_MS; - private static final long NEWS_MAX_VALIDITY_IN_MS = MAX_CACHE_VALIDITY_MS; + private static final long NEWS_MAX_VALIDITY_IN_MS = ProviderContract.getMAX_CACHE_VALIDITY_MS(); private static final long NEWS_VALIDITY_IN_MS = TimeUnit.DAYS.toMillis(1); private static final long NEWS_VALIDITY_IN_FOCUS_IN_MS = TimeUnit.HOURS.toMillis(1); private static final long NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS = TimeUnit.MINUTES.toMillis(30); @@ -920,7 +922,7 @@ public String getNewsDbTableName() { @NonNull @Override public String[] getNewsProjection() { - return NewsProviderContract.PROJECTION_NEWS; + return NewsProviderContract.getPROJECTION_NEWS(); } @Nullable diff --git a/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.kt index a0109f22..5570e1d0 100644 --- a/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.kt @@ -24,6 +24,7 @@ import org.mtransit.android.commons.UriUtils import org.mtransit.android.commons.data.News import org.mtransit.android.commons.linkifyAllURLs import org.mtransit.android.commons.provider.agency.AgencyUtils +import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.news.NewsProvider import org.mtransit.android.commons.provider.news.NewsProviderContract import org.mtransit.android.commons.provider.news.NewsTextFormatter @@ -51,7 +52,7 @@ class YouTubeNewsProvider : NewsProvider() { private const val FORCE_REFRESH = false // private const val FORCE_REFRESH = true // DEBUG - private val NEWS_MAX_VALIDITY_IN_MS = MAX_CACHE_VALIDITY_MS + private val NEWS_MAX_VALIDITY_IN_MS = ProviderContract.MAX_CACHE_VALIDITY_MS private val NEWS_VALIDITY_IN_MS = TimeUnit.DAYS.toMillis(14L) private val NEWS_VALIDITY_IN_FOCUS_IN_MS = TimeUnit.DAYS.toMillis(7L) private val NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS = TimeUnit.HOURS.toMillis(48L) @@ -194,7 +195,7 @@ class YouTubeNewsProvider : NewsProvider() { override fun getLogTag() = LOG_TAG - override fun getURI_MATCHER() = _uriMatcher + override val URI_MATCHER: UriMatcher get() = _uriMatcher private var _dbHelper: YouTubeNewsDbHelper? = null private var _currentDbVersion: Int = -1 @@ -236,14 +237,13 @@ class YouTubeNewsProvider : NewsProvider() { fun getDBHelper() = getDBHelper(ContentProviderCompat.requireContext(this)) - override fun getMinDurationBetweenNewsRefreshInMs(inFocus: Boolean) = if (inFocus) { + override fun getMinDurationBetweenNewsRefreshInMs(inFocusOrDefault: Boolean) = if (inFocusOrDefault) { NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_FOCUS_IN_MS } else NEWS_MIN_DURATION_BETWEEN_REFRESH_IN_MS - override fun getNewsMaxValidityInMs() = NEWS_MAX_VALIDITY_IN_MS - .takeUnless { FORCE_REFRESH } ?: 0L + override val newsMaxValidityInMs: Long get() = NEWS_MAX_VALIDITY_IN_MS.takeUnless { FORCE_REFRESH } ?: 0L - override fun getNewsValidityInMs(inFocus: Boolean) = if (inFocus) { + override fun getNewsValidityInMs(inFocusOrDefault: Boolean) = if (inFocusOrDefault) { NEWS_VALIDITY_IN_FOCUS_IN_MS } else NEWS_VALIDITY_IN_MS @@ -251,8 +251,8 @@ class YouTubeNewsProvider : NewsProvider() { return purgeUselessCachedNews(this) } - override fun deleteCachedNews(newsId: Int?): Boolean { - return deleteCachedNews(this, newsId) + override fun deleteCachedNews(id: Int?): Boolean { + return deleteCachedNews(this, id) } private fun deleteAllAgencyNewsData(): Int { @@ -273,9 +273,9 @@ class YouTubeNewsProvider : NewsProvider() { return affectedRows } - override fun getAuthority() = _authority + override val authority: String get() = _authority - override fun getAuthorityUri() = _authorityUri + override val authorityUri: Uri get() = _authorityUri override fun cacheNews(newNews: ArrayList) { cacheNewsS(this, newNews) @@ -575,5 +575,5 @@ class YouTubeNewsProvider : NewsProvider() { } } - override fun getNewsLanguages() = _languages + override val newsLanguages: Collection get() = _languages } \ No newline at end of file diff --git a/src/main/java/org/mtransit/android/commons/provider/agency/AgencyProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/agency/AgencyProviderContract.java index ae20ce5f..f01ecf17 100644 --- a/src/main/java/org/mtransit/android/commons/provider/agency/AgencyProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/agency/AgencyProviderContract.java @@ -4,6 +4,8 @@ public interface AgencyProviderContract extends ProviderContract { + String PING_PATH = ProviderContract.PING_PATH; + String ALL_PATH = "all"; String VERSION_PATH = "version"; String LABEL_PATH = "label"; diff --git a/src/main/java/org/mtransit/android/commons/provider/bike/BikeStationProvider.java b/src/main/java/org/mtransit/android/commons/provider/bike/BikeStationProvider.java index 97a8f14e..9defe3e2 100644 --- a/src/main/java/org/mtransit/android/commons/provider/bike/BikeStationProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/bike/BikeStationProvider.java @@ -26,7 +26,7 @@ import org.mtransit.android.commons.UriUtils; import org.mtransit.android.commons.WordUtils; import org.mtransit.android.commons.data.Area; -import org.mtransit.android.commons.data.AvailabilityPercent; +import org.mtransit.android.commons.data.AvailabilityPercentStatusFilter; import org.mtransit.android.commons.data.DataSourceTypeId; import org.mtransit.android.commons.data.POI; import org.mtransit.android.commons.data.POIStatus; @@ -255,7 +255,7 @@ public Cursor getSearchSuggest(@Nullable String query) { @Override public Cursor getPOI(@Nullable POIProviderContract.Filter poiFilter) { if (poiFilter != null && poiFilter.getExtraBoolean(POIProviderContract.POI_FILTER_EXTRA_AVOID_LOADING, false)) { - if (getLastUpdateInMs() + getPOIMaxValidityInMs() > TimeUtils.currentTimeMillis()) { // not too old to display + if (getLastUpdateInMs() + getPoiMaxValidityInMs() > TimeUtils.currentTimeMillis()) { // not too old to display Cursor cursor = getPOIFromDB(poiFilter); if (cursor != null && cursor.getCount() > 0) { return cursor; // returned cached results instead of loading while user is waiting @@ -279,16 +279,16 @@ public Cursor getPOIFromDB(@Nullable POIProviderContract.Filter poiFilter) { @Nullable @Override public POIStatus getNewStatus(@NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof AvailabilityPercent.AvailabilityPercentStatusFilter)) { + if (!(statusFilter instanceof AvailabilityPercentStatusFilter)) { MTLog.w(this, "getNewStatus() > Can't find new schedule without AvailabilityPercentStatusFilter!"); return null; } - AvailabilityPercent.AvailabilityPercentStatusFilter availabilityPercentStatusFilter = (AvailabilityPercent.AvailabilityPercentStatusFilter) statusFilter; + AvailabilityPercentStatusFilter availabilityPercentStatusFilter = (AvailabilityPercentStatusFilter) statusFilter; return getNewBikeStationStatus(availabilityPercentStatusFilter); } @Nullable - public abstract POIStatus getNewBikeStationStatus(@NonNull AvailabilityPercent.AvailabilityPercentStatusFilter filter); + public abstract POIStatus getNewBikeStationStatus(@NonNull AvailabilityPercentStatusFilter filter); @Override public void cacheStatus(@NonNull POIStatus newStatusToCache) { @@ -587,12 +587,12 @@ public BikeStationDbHelper getNewDbHelper(@NonNull Context context) { } @Override - public long getPOIMaxValidityInMs() { + public long getPoiMaxValidityInMs() { return BIKE_STATION_MAX_VALIDITY_IN_MS; } @Override - public long getPOIValidityInMs() { + public long getPoiValidityInMs() { return BIKE_STATION_VALIDITY_IN_MS; } @@ -622,7 +622,7 @@ public long getMinDurationBetweenRefreshInMs(boolean inFocus) { @NonNull @Override - public ArrayMap getPOIProjectionMap() { + public ArrayMap getPoiProjectionMap() { if (poiProjectionMap == null) { poiProjectionMap = POIProvider.getNewPoiProjectionMap(getAUTHORITY(requireContextCompat()), getAGENCY_TYPE_ID(requireContextCompat())); } @@ -631,8 +631,8 @@ public ArrayMap getPOIProjectionMap() { @NonNull @Override - public String[] getPOIProjection() { - return POIProvider.PROJECTION_POI; + public String[] getPoiProjection() { + return POIProviderContract.getPROJECTION_POI(); } @NonNull diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java deleted file mode 100644 index e43cbdd9..00000000 --- a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.mtransit.android.commons.provider.common; - -import android.content.Context; -import android.content.UriMatcher; -import android.database.sqlite.SQLiteDatabase; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import org.json.JSONException; -import org.json.JSONObject; -import org.mtransit.android.commons.JSONUtils; -import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.SecureStringUtils; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -public interface ProviderContract extends MTLog.Loggable { - - String PING_PATH = "ping"; - - long MAX_CACHE_VALIDITY_MS = TimeUnit.DAYS.toMillis(1_000L); - - @NonNull - UriMatcher getURI_MATCHER(); - - @WorkerThread - @NonNull - SQLiteDatabase getReadDB(); - - @WorkerThread - @NonNull - SQLiteDatabase getWriteDB(); - - @NonNull - Context requireContextCompat(); - - abstract class Filter { - - private static final boolean CACHE_ONLY_DEFAULT = false; - private static final boolean IN_FOCUS_DEFAULT = false; - - @Nullable - private Boolean cacheOnly = null; - @Nullable - private Long cacheValidityInMs = null; - @Nullable - private Boolean inFocus = null; - @Nullable - private Map providedEncryptKeysMap = null; - - public boolean isCacheOnlyOrDefault() { - return this.cacheOnly == null ? CACHE_ONLY_DEFAULT : this.cacheOnly; - } - - @SuppressWarnings("unused") - @Nullable - public Boolean getCacheOnly() { - return this.cacheOnly; - } - - public void setCacheOnly(@Nullable Boolean cacheOnly) { - this.cacheOnly = cacheOnly; - } - - @Nullable - public Long getCacheValidityInMs() { - return this.cacheValidityInMs; - } - - @SuppressWarnings("unused") - public void setCacheValidityInMs(@Nullable Long cacheValidityInMs) { - this.cacheValidityInMs = cacheValidityInMs; - } - - public boolean isInFocusOrDefault() { - return this.inFocus == null ? IN_FOCUS_DEFAULT : this.inFocus; - } - - @SuppressWarnings("unused") - @Nullable - public Boolean getInFocus() { - return this.inFocus; - } - - public void setInFocus(@Nullable Boolean inFocus) { - this.inFocus = inFocus; - } - - void setProvidedEncryptKeysMap(@Nullable Map providedEncryptKeysMap) { - this.providedEncryptKeysMap = providedEncryptKeysMap; - } - - @SuppressWarnings("unused") - public boolean hasProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap != null && !this.providedEncryptKeysMap.isEmpty(); - } - - @SuppressWarnings("unused") - @Nullable - public Map getProvidedEncryptKeysMap() { - return this.providedEncryptKeysMap; - } - - @Nullable - public String getProvidedEncryptKey(@NonNull String key) { - if (this.providedEncryptKeysMap == null) return null; - final String value = this.providedEncryptKeysMap.get(key); - if (value == null || value.trim().isEmpty()) return null; - return value; - } - - public void setProvidedKeys(@Nullable Map keysMap) { - final Map providedEncryptKeysMap = new HashMap<>(); - if (keysMap != null) { - for (Map.Entry entry : keysMap.entrySet()) { - final String enc = SecureStringUtils.enc(entry.getValue()); - if (enc == null) continue; - providedEncryptKeysMap.put(entry.getKey(), enc); - } - } - setProvidedEncryptKeysMap(providedEncryptKeysMap); - } - - private static final String JSON_CACHE_ONLY = "cacheOnly"; - private static final String JSON_CACHE_VALIDITY_IN_MS = "cacheValidityInMs"; - private static final String JSON_IN_FOCUS = "inFocus"; - private static final String JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap"; - - @Nullable - @SuppressWarnings("unused") - public static Long getCacheValidityInMsFromJSON(@NonNull JSONObject json) throws JSONException { - return json.has(JSON_CACHE_VALIDITY_IN_MS) ? json.getLong(JSON_CACHE_VALIDITY_IN_MS) : null; - } - - @SuppressWarnings("unused") - @Nullable - public abstract String toJSONString(); - - public static void toJSON(@NonNull Filter filter, @NonNull JSONObject json) throws JSONException { - if (filter.cacheOnly != null) { - json.put(JSON_CACHE_ONLY, filter.cacheOnly); - } - if (filter.cacheValidityInMs != null) { - json.put(JSON_CACHE_VALIDITY_IN_MS, filter.cacheValidityInMs); - } - if (filter.inFocus != null) { - json.put(JSON_IN_FOCUS, filter.inFocus); - } - if (filter.providedEncryptKeysMap != null) { - json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(filter.providedEncryptKeysMap)); - } - } - - public static void fromJSON(@NonNull Filter filter, @NonNull JSONObject json) throws JSONException { - if (json.has(JSON_CACHE_ONLY)) { - filter.cacheOnly = json.getBoolean(JSON_CACHE_ONLY); - } - if (json.has(JSON_CACHE_VALIDITY_IN_MS)) { - filter.cacheValidityInMs = json.getLong(JSON_CACHE_VALIDITY_IN_MS); - } - if (json.has(JSON_IN_FOCUS)) { - filter.inFocus = json.getBoolean(JSON_IN_FOCUS); - } - if (json.has(JSON_PROVIDED_ENCRYPT_KEYS_MAP)) { - filter.providedEncryptKeysMap = JSONUtils.toMapOfStrings(json.getJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)); - } - } - - @NonNull - protected String toStringParts() { - final StringBuilder sb = new StringBuilder(); - if (this.cacheOnly != null) sb.append("cacheOnly:").append(this.cacheOnly).append(','); - if (this.cacheValidityInMs != null) sb.append("cacheValidityInMs:").append(MTLog.formatDuration(this.cacheValidityInMs)).append(','); - if (this.inFocus != null) sb.append("inFocus:").append(this.inFocus).append(","); - return sb.toString(); - } - } -} diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt new file mode 100644 index 00000000..476d8dd4 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt @@ -0,0 +1,100 @@ +package org.mtransit.android.commons.provider.common + +import android.content.Context +import android.content.UriMatcher +import android.database.sqlite.SQLiteDatabase +import androidx.annotation.WorkerThread +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.JSONUtils +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.MTLog.Loggable +import org.mtransit.android.commons.SecureStringUtils.enc +import org.mtransit.android.commons.optBoolean +import org.mtransit.android.commons.optLong +import java.util.concurrent.TimeUnit + +interface ProviderContract : Loggable { + + companion object { + const val PING_PATH: String = "ping" + + @JvmStatic + val MAX_CACHE_VALIDITY_MS = TimeUnit.DAYS.toMillis(1000L) + } + + @Suppress("PropertyName") + val URI_MATCHER: UriMatcher + + @get:WorkerThread + val readDB: SQLiteDatabase + + @get:WorkerThread + val writeDB: SQLiteDatabase + + fun requireContextCompat(): Context + + abstract class Filter { + + companion object { + private const val CACHE_ONLY_DEFAULT = false + private const val IN_FOCUS_DEFAULT = false + + private const val JSON_CACHE_ONLY = "cacheOnly" + private const val JSON_CACHE_VALIDITY_IN_MS = "cacheValidityInMs" + private const val JSON_IN_FOCUS = "inFocus" + private const val JSON_PROVIDED_ENCRYPT_KEYS_MAP = "providedEncryptKeysMap" + + @Throws(JSONException::class) + fun getCacheOnlyFromJSON(json: JSONObject) = json.optBoolean(JSON_CACHE_ONLY, null) + + @Throws(JSONException::class) + fun getInFocusFromJSON(json: JSONObject) = json.optBoolean(JSON_IN_FOCUS, null) + + @Throws(JSONException::class) + fun getCacheValidityInMsFromJSON(json: JSONObject) = json.optLong(JSON_CACHE_VALIDITY_IN_MS, null) + + @Throws(JSONException::class) + fun getProvidedEncryptKeysMapFromJSON(json: JSONObject): Map? = + json.optJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)?.let { JSONUtils.toMapOfStrings(it) } + + @JvmStatic + @Throws(JSONException::class) + fun toJSON(filter: Filter, json: JSONObject) = filter.apply { + cacheOnly?.let { json.put(JSON_CACHE_ONLY, it) } + cacheValidityInMs?.let { json.put(JSON_CACHE_VALIDITY_IN_MS, it) } + inFocus?.let { json.put(JSON_IN_FOCUS, it) } + providedEncryptKeysMap?.let { json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(it)) } + } + + fun toProvidedKeys(keysMap: Map?) = keysMap?.mapNotNull { + val enc = enc(it.value) ?: return@mapNotNull null + it.key to enc + }?.toMap() + } + + abstract val cacheOnly: Boolean? + val isCacheOnlyOrDefault: Boolean get() = this.cacheOnly ?: CACHE_ONLY_DEFAULT + + abstract val cacheValidityInMs: Long? + + abstract val inFocus: Boolean? + val isInFocusOrDefault: Boolean get() = this.inFocus ?: IN_FOCUS_DEFAULT + + abstract val providedEncryptKeysMap: Map? + + fun getProvidedEncryptKey(key: String) = + this.providedEncryptKeysMap?.get(key = key) + ?.takeIf { it.trim().isNotEmpty() } + + @Suppress("unused") + abstract fun toJSONString(): String? + + fun toStringParts() = buildString { + cacheOnly?.let { append("cacheOnly:").append(it).append(",") } + cacheValidityInMs?.let { append("cacheValidityInMs:").append(MTLog.formatDuration(it)).append(",") } + inFocus?.let { append("inFocus:").append(it).append(",") } + providedEncryptKeysMap?.let { append("providedEncryptKeysMap:").append(it.size).append(",") } // no not print keys + } + } +} diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java index 071f65cc..76915423 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSPOIProvider.java @@ -122,7 +122,7 @@ public static Cursor getPOIFromDB(@NonNull GTFSProvider provider, @Nullable POIP } SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(GTFSRDSProvider.ROUTE_DIRECTION_DIRECTION_STOPS_STOP_JOIN); - ArrayMap poiProjectionMap = provider.getPOIProjectionMap(); + ArrayMap poiProjectionMap = provider.getPoiProjectionMap(); boolean searchKeywordsAdded = false; if (POIProviderContract.Filter.isSearchKeywords(poiFilter) && poiFilter.getSearchKeywords() != null) { poiProjectionMap = new ArrayMap<>(poiProjectionMap); // clone to avoid updating shared static map @@ -133,7 +133,7 @@ public static Cursor getPOIFromDB(@NonNull GTFSProvider provider, @Nullable POIP } qb.setProjectionMap(poiProjectionMap); - String[] poiProjection = provider.getPOIProjection(); + String[] poiProjection = provider.getPoiProjection(); if (searchKeywordsAdded) { poiProjection = ArrayUtils.addAllNonNull(poiProjection, new String[]{POIProviderContract.Columns.T_POI_K_SCORE_META_OPT}); // makes new array } @@ -238,13 +238,13 @@ public static String getPOITable(@SuppressWarnings("unused") @NonNull GTFSProvid return null; // USING CUSTOM TABLE } - private static final long POI_MAX_VALIDITY_IN_MS = ProviderContract.MAX_CACHE_VALIDITY_MS; + private static final long POI_MAX_VALIDITY_IN_MS = ProviderContract.getMAX_CACHE_VALIDITY_MS(); public static long getPOIMaxValidityInMs(@SuppressWarnings("unused") @NonNull GTFSProvider provider) { return POI_MAX_VALIDITY_IN_MS; } - private static final long POI_VALIDITY_IN_MS = ProviderContract.MAX_CACHE_VALIDITY_MS; + private static final long POI_VALIDITY_IN_MS = ProviderContract.getMAX_CACHE_VALIDITY_MS(); public static long getPOIValidityInMs(@SuppressWarnings("unused") @NonNull GTFSProvider provider) { return POI_VALIDITY_IN_MS; diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java index a5d13855..ee3ce4c4 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStatusProvider.java @@ -23,6 +23,7 @@ import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.data.RouteDirectionStop; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.provider.GTFSProvider; import org.mtransit.android.commons.provider.agency.AgencyUtils; import org.mtransit.android.commons.provider.status.StatusProvider; @@ -143,11 +144,11 @@ public static long getMinDurationBetweenRefreshInMs(boolean inFocus) { @Nullable public static POIStatus getNewStatus(@NonNull GTFSProvider provider, @NonNull StatusProviderContract.Filter statusFilter) { - if (!(statusFilter instanceof Schedule.ScheduleStatusFilter)) { + if (!(statusFilter instanceof ScheduleStatusFilter)) { MTLog.w(LOG_TAG, "Can't find new schedule without schedule filter!"); return null; } - final Schedule.ScheduleStatusFilter scheduleStatusFilter = (Schedule.ScheduleStatusFilter) statusFilter; + final ScheduleStatusFilter scheduleStatusFilter = (ScheduleStatusFilter) statusFilter; final Schedule schedule = new Schedule( null, scheduleStatusFilter.getTargetUUID(), @@ -235,7 +236,7 @@ private static String getROUTE_FREQUENCY_RAW_FILE_FORMAT(@NonNull Context contex private static final int GTFS_ROUTE_FREQUENCY_FILE_COL_COUNT = 5; @NonNull - private static ArrayList findTimestamps(@NonNull GTFSProvider provider, @NonNull Schedule.ScheduleStatusFilter filter) { + private static ArrayList findTimestamps(@NonNull GTFSProvider provider, @NonNull ScheduleStatusFilter filter) { ArrayList allTimestamps = new ArrayList<>(); final RouteDirectionStop rds = filter.getRouteDirectionStop(); final int maxDataRequests = filter.getMaxDataRequestsOrDefault(); @@ -596,7 +597,7 @@ protected static Set filterServiceIdOrInts(@NonNull Set findFrequencies(@NonNull GTFSProvider provider, @NonNull Schedule.ScheduleStatusFilter filter) { + private static ArrayList findFrequencies(@NonNull GTFSProvider provider, @NonNull ScheduleStatusFilter filter) { final ArrayList allFrequencies = new ArrayList<>(); final RouteDirectionStop rds = filter.getRouteDirectionStop(); final int maxDataRequests = filter.getMaxDataRequestsOrDefault(); diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStringsUtils.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStringsUtils.kt index ad32be5c..2fa87e82 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStringsUtils.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GTFSStringsUtils.kt @@ -20,6 +20,7 @@ object GTFSStringsUtils : MTLog.Loggable { @Suppress("DiscouragedApi") @JvmStatic fun > updateStrings(timestamps: T, gtfsProvider: GTFSProvider): T { + @Suppress("SimplifyBooleanWithConstants") if (!FeatureFlags.F_EXPORT_STRINGS && !FeatureFlags.F_EXPORT_SCHEDULE_STRINGS) return timestamps val stringIds = timestamps .mapNotNull { it.headsignValue?.split(GTFSCommons.STRINGS_SEPARATOR) } diff --git a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt index e9365f37..4eeb1f88 100644 --- a/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/gtfs/GtfsStatusProviderExt.kt @@ -6,6 +6,7 @@ import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.UriUtils import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.Schedule +import org.mtransit.android.commons.data.ScheduleStatusFilter import org.mtransit.android.commons.provider.status.StatusProviderContract import kotlin.time.Duration import kotlin.time.Duration.Companion.hours @@ -42,11 +43,12 @@ fun Context.getRDSSchedule( StatusProviderContract.STATUS_PATH ), StatusProviderContract.PROJECTION_STATUS, - Schedule.ScheduleStatusFilter(rds).apply { - setLookBehindInMs(lookBehind.inWholeMilliseconds) - setMaxDataRequests(maxDataRequest) - setIncludeCancelledTimestamps(includeCancelledTimestamps) - }.let { it.toJSONStringStatic(it) }, + ScheduleStatusFilter( + routeDirectionStop = rds, + lookBehindInMs = lookBehind.inWholeMilliseconds, + maxDataRequests = maxDataRequest, + includeCancelledTimestamps = includeCancelledTimestamps + ).toJSONString(), null, null ).use { cursor -> diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java b/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java index 3e342f9e..ccf93ee1 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProvider.java @@ -156,7 +156,7 @@ private static Cursor getNews(@NonNull NewsProviderContract provider, @Nullable if (newsFilter == null) { return getNewsCursor(null); } - if (NewsProviderContract.Filter.isUUIDFilter(newsFilter)) { + if (newsFilter.isArticlesUUIDFilter()) { return provider.getNewsFromDB(newsFilter); } final long nowInMs = TimeUtils.currentTimeMillis(); @@ -246,7 +246,7 @@ private static Cursor getNewsCursor(ArrayList news) { if (news == null) { return ContentProviderConstants.EMPTY_CURSOR; } - MatrixCursor matrixCursor = new MatrixCursor(NewsProviderContract.PROJECTION_NEWS); + MatrixCursor matrixCursor = new MatrixCursor(NewsProviderContract.getPROJECTION_NEWS()); for (News oneNews : news) { matrixCursor.addRow(oneNews.getCursorRow()); } @@ -262,7 +262,7 @@ public String getNewsDbTableName() { @NonNull @Override public String[] getNewsProjection() { - return NewsProviderContract.PROJECTION_NEWS; + return NewsProviderContract.getPROJECTION_NEWS(); } @Nullable @@ -349,7 +349,7 @@ public Uri insertMT(@NonNull Uri uri, @Nullable ContentValues values) { } @SuppressWarnings("UnusedReturnValue") - public static synchronized int cacheNewsS(NewsProviderContract provider, ArrayList newNews) { + public static synchronized int cacheNewsS(@NonNull NewsProviderContract provider, @Nullable ArrayList newNews) { int affectedRows = 0; SQLiteDatabase db = null; try { diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java deleted file mode 100644 index 12100ef7..00000000 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.java +++ /dev/null @@ -1,373 +0,0 @@ -package org.mtransit.android.commons.provider.news; - -import android.database.Cursor; -import android.net.Uri; -import android.provider.BaseColumns; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.collection.ArrayMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.SqlUtils; -import org.mtransit.android.commons.data.News; -import org.mtransit.android.commons.data.POI; -import org.mtransit.android.commons.data.RouteDirectionStop; -import org.mtransit.android.commons.provider.common.ProviderContract; -import org.mtransit.commons.CollectionUtils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public interface NewsProviderContract extends ProviderContract { - - String NEWS_PATH = "news"; - - boolean REMOVE_IMAGE_FROM_TEXT = false; // TODO later - - @NonNull - String getAuthority(); - - @NonNull - Uri getAuthorityUri(); - - @Nullable - Cursor getNewsFromDB(@NonNull Filter newsFilter); - - @NonNull - String getNewsDbTableName(); - - @NonNull - String[] getNewsProjection(); - - @NonNull - ArrayMap getNewsProjectionMap(); - - void cacheNews(@NonNull ArrayList newNews); - - @Nullable - ArrayList getCachedNews(@NonNull Filter newsFilter); - - @Nullable - ArrayList getNewNews(@NonNull Filter newsFilter); - - @SuppressWarnings("UnusedReturnValue") - boolean purgeUselessCachedNews(); - - @SuppressWarnings("UnusedReturnValue") - boolean deleteCachedNews(@Nullable Integer id); - - long getNewsMaxValidityInMs(); - - long getNewsValidityInMs(boolean inFocusOrDefault); - - long getMinDurationBetweenNewsRefreshInMs(boolean inFocusOrDefault); - - @NonNull - Collection getNewsLanguages(); - - interface Columns { - String T_NEWS_K_ID = BaseColumns._ID; - String T_NEWS_K_AUTHORITY_META = "authority"; - String T_NEWS_K_UUID = "uuid"; - String T_NEWS_K_SEVERITY = "severity"; - String T_NEWS_K_NOTEWORTHY = "noteworthy"; - String T_NEWS_K_LAST_UPDATE = "last_update"; - String T_NEWS_K_CREATED_AT = "created_at"; - String T_NEWS_K_MAX_VALIDITY_IN_MS = "max_validity"; - String T_NEWS_K_TARGET_UUID = "target"; - String T_NEWS_K_COLOR = "color"; - String T_NEWS_K_AUTHOR_NAME = "author_name"; - String T_NEWS_K_AUTHOR_USERNAME = "author_username"; - String T_NEWS_K_AUTHOR_PICTURE_URL = "author_picture_url"; - String T_NEWS_K_AUTHOR_PROFILE_URL = "author_profile_url"; - String T_NEWS_K_TEXT = "text"; - String T_NEWS_K_TEXT_HTML = "text_html"; - String T_NEWS_K_WEB_URL = "web_url"; - String T_NEWS_K_LANGUAGE = "lang"; - String T_NEWS_K_SOURCE_ID = "source_id"; - String T_NEWS_K_SOURCE_LABEL = "source_label"; - String T_NEWS_K_IMAGE_URLS_COUNT = "image_urls_count"; - String T_NEWS_K_IMAGE_URL_INDEX = "image_urls_"; - } - - String[] PROJECTION_NEWS = new String[]{ // - Columns.T_NEWS_K_ID, // - Columns.T_NEWS_K_AUTHORITY_META, // - Columns.T_NEWS_K_UUID, // - Columns.T_NEWS_K_SEVERITY, // - Columns.T_NEWS_K_NOTEWORTHY, // - Columns.T_NEWS_K_LAST_UPDATE, // - Columns.T_NEWS_K_MAX_VALIDITY_IN_MS, // - Columns.T_NEWS_K_CREATED_AT, // - Columns.T_NEWS_K_TARGET_UUID, // - Columns.T_NEWS_K_COLOR, // - Columns.T_NEWS_K_AUTHOR_NAME, // - Columns.T_NEWS_K_AUTHOR_USERNAME, // - Columns.T_NEWS_K_AUTHOR_PICTURE_URL, // - Columns.T_NEWS_K_AUTHOR_PROFILE_URL, // - Columns.T_NEWS_K_TEXT, // - Columns.T_NEWS_K_TEXT_HTML, // - Columns.T_NEWS_K_WEB_URL, // - Columns.T_NEWS_K_LANGUAGE, // - Columns.T_NEWS_K_SOURCE_ID, // - Columns.T_NEWS_K_SOURCE_LABEL, // - Columns.T_NEWS_K_IMAGE_URLS_COUNT, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 0, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 1, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 2, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 3, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 4, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 5, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 6, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 7, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 8, // - Columns.T_NEWS_K_IMAGE_URL_INDEX + 9, // - }; - - @SuppressWarnings("WeakerAccess") - class Filter extends ProviderContract.Filter implements MTLog.Loggable { - - private static final String LOG_TAG = NewsProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - @Nullable - private List articlesUUIDs; // article UUIDs - @Nullable - private List targetsUUIDs; // POI UUIDs - @Nullable - private Long minCreatedAtInMs = null; - - private Filter() { - } - - @NonNull - public static Filter getNewEmptyFilter() { - return new Filter(); - } - - @NonNull - public static Filter getNewArticleUUIDFilter(@NonNull String articleUUID) { - return getNewArticlesUUIDsFilter(Collections.singletonList(articleUUID)); - } - - @NonNull - public static Filter getNewArticlesUUIDsFilter(@Nullable List articlesUUIDs) { - return new Filter().setArticlesUUIDs(articlesUUIDs); - } - - @NonNull - private Filter setArticlesUUIDs(@Nullable List articlesUUIDs) { - if (articlesUUIDs == null || articlesUUIDs.isEmpty()) { - throw new UnsupportedOperationException("Need at least 1 article uuid!"); - } - this.articlesUUIDs = articlesUUIDs; - return this; - } - - @SuppressWarnings("unused") - @Nullable - public List getArticlesUUIDs() { - return articlesUUIDs; - } - - @NonNull - public static Filter getNewTargetUUIDsFilter(@NonNull POI poi) { - return getNewTargetsUUIDsFilter(makeTargetsUUIDs(poi)); - } - - @NonNull - public static ArrayList makeTargetsUUIDs(@NonNull POI poi) { - final ArrayList targetsUUIDs = new ArrayList<>(); - targetsUUIDs.add(poi.getAuthority()); - if (poi instanceof RouteDirectionStop) { - targetsUUIDs.add(POI.POIUtils.makeUUID(poi.getAuthority(), ((RouteDirectionStop) poi).getRoute().getId())); - } - return targetsUUIDs; - } - - @SuppressWarnings("unused") - @NonNull - public static Filter getNewTargetUUIDsFilter(@NonNull String targetUUID) { - return getNewTargetsUUIDsFilter(Collections.singletonList(targetUUID)); - } - - @NonNull - public static Filter getNewTargetsUUIDsFilter(@Nullable List targetsUUIDs) { - return new Filter().setTargetsUUIDs(targetsUUIDs); - } - - @NonNull - private Filter setTargetsUUIDs(List targetsUUIDs) { - if (targetsUUIDs == null || targetsUUIDs.isEmpty()) { - throw new UnsupportedOperationException("Need at least 1 target!"); - } - this.targetsUUIDs = targetsUUIDs; - return this; - } - - @SuppressWarnings("unused") - @Nullable - public List getTargetsUUIDs() { - return targetsUUIDs; - } - - @NonNull - public Filter setMinCreatedAtInMs(long minCreatedAtInMs) { - this.minCreatedAtInMs = minCreatedAtInMs; - return this; - } - - @Nullable - public Long getMinCreatedAtInMsOrNull() { - return this.minCreatedAtInMs; - } - - @NonNull - @Override - public String toString() { - StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); - if (isUUIDFilter(this)) { - sb.append("articleUUIDs:").append(this.articlesUUIDs).append(','); - } else if (isTargetFilter(this)) { - sb.append("targetsUUIDs:").append(this.targetsUUIDs).append(','); - } - sb.append(super.toStringParts()); - sb.append("minCreatedAtInMs:").append(this.minCreatedAtInMs); - sb.append(']'); - return sb.toString(); - } - - @SuppressWarnings("unused") - @NonNull - public String toStringTargetsAndUuid() { - final StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); - if (isUUIDFilter(this)) { - sb.append("articleUUIDs:").append(this.articlesUUIDs).append(','); - } else if (isTargetFilter(this)) { - sb.append("targetsUUIDs:").append(this.targetsUUIDs).append(','); - } - sb.append(']'); - return sb.toString(); - } - - public static boolean isUUIDFilter(@Nullable Filter newsFilter) { - return newsFilter != null && CollectionUtils.getSize(newsFilter.articlesUUIDs) > 0; - } - - public static boolean isTargetFilter(@Nullable Filter newsFilter) { - return newsFilter != null && CollectionUtils.getSize(newsFilter.targetsUUIDs) > 0; - } - - @NonNull - public String getSqlSelection(@NonNull String uuidTableColumn, @NonNull String targetColumn, @NonNull String createdAtColumn) { - StringBuilder sb = new StringBuilder(); - if (isUUIDFilter(this)) { - sb.append(SqlUtils.getWhereInString(uuidTableColumn, this.articlesUUIDs)); - } else if (isTargetFilter(this)) { - sb.append(SqlUtils.getWhereInString(targetColumn, this.targetsUUIDs)); - } - if (getMinCreatedAtInMsOrNull() != null) { - if (sb.length() > 0) { - sb.append(SqlUtils.AND); - } - sb.append(SqlUtils.getWhereSuperior(createdAtColumn, getMinCreatedAtInMsOrNull())); - } - return sb.toString(); - } - - @Nullable - public static Filter fromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? null : fromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return null; - } - } - - private static final String JSON_ARTICLE_UUIDS = "uuids"; // article UUIDs - private static final String JSON_TARGETS_UUIDS = "targets"; // POI UUIDs - private static final String JSON_MIN_CREATED_AT_IN_MS = "minCreatedAtInMs"; - - @Nullable - public static Filter fromJSON(@NonNull JSONObject json) { - try { - final Filter newsFilter = new Filter(); - ProviderContract.Filter.fromJSON(newsFilter, json); - final JSONArray jArticleUUIDs = json.optJSONArray(JSON_ARTICLE_UUIDS); - final JSONArray jTargetsUUIDs = json.optJSONArray(JSON_TARGETS_UUIDS); - if (jArticleUUIDs != null && jArticleUUIDs.length() > 0) { - final ArrayList articleUUIDs = new ArrayList<>(); - for (int i = 0; i < jArticleUUIDs.length(); i++) { - articleUUIDs.add(jArticleUUIDs.getString(i)); - } - newsFilter.setArticlesUUIDs(articleUUIDs); - } else if (jTargetsUUIDs != null && jTargetsUUIDs.length() > 0) { - final ArrayList targetsUUIDs = new ArrayList<>(); - for (int i = 0; i < jTargetsUUIDs.length(); i++) { - targetsUUIDs.add(jTargetsUUIDs.getString(i)); - } - newsFilter.setTargetsUUIDs(targetsUUIDs); - } - if (json.has(JSON_MIN_CREATED_AT_IN_MS)) { - newsFilter.minCreatedAtInMs = json.getLong(JSON_MIN_CREATED_AT_IN_MS); - } - return newsFilter; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); - return null; - } - } - - @Nullable - @Override - public String toJSONString() { - return toJSONString(this); - } - - @Nullable - public static String toJSONString(@NonNull Filter newsFilter) { - final JSONObject json = toJSON(newsFilter); - return json == null ? null : json.toString(); - } - - @Nullable - public static JSONObject toJSON(@NonNull Filter newsFilter) { - try { - final JSONObject json = new JSONObject(); - ProviderContract.Filter.toJSON(newsFilter, json); - if (newsFilter.getMinCreatedAtInMsOrNull() != null) { - json.put(JSON_MIN_CREATED_AT_IN_MS, newsFilter.getMinCreatedAtInMsOrNull()); - } - if (isUUIDFilter(newsFilter) && newsFilter.articlesUUIDs != null) { - final JSONArray jArticleUUIDs = new JSONArray(); - for (String articleUUID : newsFilter.articlesUUIDs) { - jArticleUUIDs.put(articleUUID); - } - json.put(JSON_ARTICLE_UUIDS, jArticleUUIDs); - } else if (isTargetFilter(newsFilter) && newsFilter.targetsUUIDs != null) { - final JSONArray jTargetUUIDs = new JSONArray(); - for (String targetUUID : newsFilter.targetsUUIDs) { - jTargetUUIDs.put(targetUUID); - } - json.put(JSON_TARGETS_UUIDS, jTargetUUIDs); - } - return json; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", newsFilter); - return null; - } - } - } -} diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt new file mode 100644 index 00000000..e40f2597 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt @@ -0,0 +1,279 @@ +package org.mtransit.android.commons.provider.news + +import android.annotation.SuppressLint +import android.database.Cursor +import android.net.Uri +import android.provider.BaseColumns +import androidx.annotation.Discouraged +import androidx.collection.ArrayMap +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.SqlUtils +import org.mtransit.android.commons.data.News +import org.mtransit.android.commons.data.POI +import org.mtransit.android.commons.data.RouteDirectionStop +import org.mtransit.android.commons.provider.common.ProviderContract + +interface NewsProviderContract : ProviderContract { + + companion object { + const val NEWS_PATH = "news" + + const val REMOVE_IMAGE_FROM_TEXT: Boolean = false // TODO later + + @JvmStatic + val PROJECTION_NEWS = arrayOf( + Columns.T_NEWS_K_ID, + Columns.T_NEWS_K_AUTHORITY_META, + Columns.T_NEWS_K_UUID, + Columns.T_NEWS_K_SEVERITY, + Columns.T_NEWS_K_NOTEWORTHY, + Columns.T_NEWS_K_LAST_UPDATE, + Columns.T_NEWS_K_MAX_VALIDITY_IN_MS, + Columns.T_NEWS_K_CREATED_AT, + Columns.T_NEWS_K_TARGET_UUID, + Columns.T_NEWS_K_COLOR, + Columns.T_NEWS_K_AUTHOR_NAME, + Columns.T_NEWS_K_AUTHOR_USERNAME, + Columns.T_NEWS_K_AUTHOR_PICTURE_URL, + Columns.T_NEWS_K_AUTHOR_PROFILE_URL, + Columns.T_NEWS_K_TEXT, + Columns.T_NEWS_K_TEXT_HTML, + Columns.T_NEWS_K_WEB_URL, + Columns.T_NEWS_K_LANGUAGE, + Columns.T_NEWS_K_SOURCE_ID, + Columns.T_NEWS_K_SOURCE_LABEL, + Columns.T_NEWS_K_IMAGE_URLS_COUNT, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 0, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 1, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 2, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 3, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 4, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 5, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 6, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 7, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 8, + Columns.T_NEWS_K_IMAGE_URL_INDEX + 9, + ) + } + + val authority: String + + val authorityUri: Uri + + fun getNewsFromDB(newsFilter: Filter): Cursor? + + val newsDbTableName: String + + val newsProjection: Array + + val newsProjectionMap: ArrayMap + + fun cacheNews(newNews: ArrayList) + + fun getCachedNews(newsFilter: Filter): ArrayList? + + fun getNewNews(newsFilter: Filter): ArrayList? + + fun purgeUselessCachedNews(): Boolean + + fun deleteCachedNews(id: Int?): Boolean + + val newsMaxValidityInMs: Long + + fun getNewsValidityInMs(inFocusOrDefault: Boolean): Long + + fun getMinDurationBetweenNewsRefreshInMs(inFocusOrDefault: Boolean): Long + + val newsLanguages: Collection + + interface Columns { + companion object { + const val T_NEWS_K_ID: String = BaseColumns._ID + const val T_NEWS_K_AUTHORITY_META = "authority" + const val T_NEWS_K_UUID = "uuid" + const val T_NEWS_K_SEVERITY = "severity" + const val T_NEWS_K_NOTEWORTHY = "noteworthy" + const val T_NEWS_K_LAST_UPDATE = "last_update" + const val T_NEWS_K_CREATED_AT = "created_at" + const val T_NEWS_K_MAX_VALIDITY_IN_MS = "max_validity" + const val T_NEWS_K_TARGET_UUID = "target" + const val T_NEWS_K_COLOR = "color" + const val T_NEWS_K_AUTHOR_NAME = "author_name" + const val T_NEWS_K_AUTHOR_USERNAME = "author_username" + const val T_NEWS_K_AUTHOR_PICTURE_URL = "author_picture_url" + const val T_NEWS_K_AUTHOR_PROFILE_URL = "author_profile_url" + const val T_NEWS_K_TEXT = "text" + const val T_NEWS_K_TEXT_HTML = "text_html" + const val T_NEWS_K_WEB_URL = "web_url" + const val T_NEWS_K_LANGUAGE = "lang" + const val T_NEWS_K_SOURCE_ID = "source_id" + const val T_NEWS_K_SOURCE_LABEL = "source_label" + const val T_NEWS_K_IMAGE_URLS_COUNT = "image_urls_count" + const val T_NEWS_K_IMAGE_URL_INDEX = "image_urls_" + } + } + + data class Filter @Discouraged("use secondary constructor() instead") constructor( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, + private val articlesUUIDs: List? = null, + private val targetsUUIDs: List? = null, + private val minCreatedAtInMs: Long? = null, + ) : ProviderContract.Filter(), MTLog.Loggable { + + companion object { + private val LOG_TAG = NewsProviderContract::class.java.getSimpleName() + ">" + Filter::class.java.getSimpleName() + + @SuppressLint("DiscouragedApi") + @JvmStatic + fun newEmptyFilter() = Filter() + + @SuppressLint("DiscouragedApi") + @JvmStatic + fun newArticleUUIDFilter(articleUUID: String) = Filter(articlesUUIDs = listOf(articleUUID)) + + @SuppressLint("DiscouragedApi") + @JvmStatic + fun newPOIFilter(poi: POI) = Filter(targetsUUIDs = poi.toTargetsUUIDs()) + + @JvmStatic + fun POI.toTargetsUUIDs(): List = buildList { + add(getAuthority()) + if (this@toTargetsUUIDs is RouteDirectionStop) { + add(POI.POIUtils.makeUUID(authority, route.id)) + } + } + + @JvmStatic + fun fromJSONString(jsonString: String?): Filter? { + try { + return jsonString?.let { fromJSON(JSONObject(it)) } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString) + return null + } + } + + private const val JSON_ARTICLE_UUIDS = "uuids" // article UUIDs + private const val JSON_TARGETS_UUIDS = "targets" // POI UUIDs + private const val JSON_MIN_CREATED_AT_IN_MS = "minCreatedAtInMs" + + fun fromJSON(json: JSONObject): Filter? { + try { + //noinspection DiscouragedApi + return Filter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + articlesUUIDs = json.optJSONArray(JSON_ARTICLE_UUIDS)?.takeIf { it.length() > 0 }?.let { jArticleUUIDs -> + buildList { + for (i in 0.. 0 }?.let { jTargetUUIDs -> + buildList { + for (i in 0.. poiProjectionMap = provider.getPOIProjectionMap(); + ArrayMap poiProjectionMap = provider.getPoiProjectionMap(); if (POIProviderContract.Filter.isSearchKeywords(poiFilter) && poiFilter.getSearchKeywords() != null) { SqlUtils.appendProjection(poiProjectionMap, POIProviderContract.Filter.getSearchSelectionScore(poiFilter.getSearchKeywords(), SEARCHABLE_LIKE_COLUMNS, SEARCHABLE_EQUALS_COLUMNS), POIProviderContract.Columns.T_POI_K_SCORE_META_OPT); } qb.setProjectionMap(poiProjectionMap); - String[] poiProjection = provider.getPOIProjection(); + String[] poiProjection = provider.getPoiProjection(); if (POIProviderContract.Filter.isSearchKeywords(poiFilter)) { poiProjection = ArrayUtils.addAll(poiProjection, new String[]{POIProviderContract.Columns.T_POI_K_SCORE_META_OPT}); } @@ -291,15 +294,15 @@ public static Cursor getDefaultPOIFromDB(@Nullable POIProviderContract.Filter po @NonNull @Override - public String[] getPOIProjection() { - return PROJECTION_POI; + public String[] getPoiProjection() { + return POIProviderContract.getPROJECTION_POI(); } private static ArrayMap poiProjectionMap; @NonNull @Override - public ArrayMap getPOIProjectionMap() { + public ArrayMap getPoiProjectionMap() { if (poiProjectionMap == null) { poiProjectionMap = getNewPoiProjectionMap(getAUTHORITY(requireContextCompat()), getTYPE_ID(requireContextCompat())); } @@ -388,12 +391,12 @@ public int updateMT(@NonNull Uri uri, @Nullable ContentValues values, @Nullable @Nullable @Override - public Uri insertMT(@NonNull Uri uri, ContentValues values) { + public Uri insertMT(@NonNull Uri uri, @Nullable ContentValues values) { MTLog.w(this, "The insert method is not available."); return null; } - public static synchronized int insertDefaultPOIs(@NonNull POIProviderContract provider, Collection defaultPOIs) { + public static synchronized int insertDefaultPOIs(@NonNull POIProviderContract provider, @Nullable Collection defaultPOIs) { int affectedRows = 0; SQLiteDatabase db = null; try { diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java deleted file mode 100644 index a30551d7..00000000 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.java +++ /dev/null @@ -1,706 +0,0 @@ -package org.mtransit.android.commons.provider.poi; - -import android.app.SearchManager; -import android.database.Cursor; -import android.provider.BaseColumns; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.collection.ArrayMap; -import androidx.collection.SimpleArrayMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.mtransit.android.commons.ArrayUtils; -import org.mtransit.android.commons.LocationUtils; -import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.SqlUtils; -import org.mtransit.android.commons.StringUtils; -import org.mtransit.android.commons.provider.common.ContentProviderConstants; -import org.mtransit.android.commons.provider.common.ProviderContract; -import org.mtransit.commons.CollectionUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Locale; - -public interface POIProviderContract extends ProviderContract { - - String POI_PATH = "poi"; - - String POI_FILTER_EXTRA_AVOID_LOADING = "avoidLoading"; - - String POI_FILTER_EXTRA_SORT_ORDER = "sortOrder"; - - long getPOIMaxValidityInMs(); - - long getPOIValidityInMs(); - - @Nullable - Cursor getPOI(@Nullable Filter poiFilter); - - @Nullable - Cursor getPOIFromDB(@Nullable Filter poiFilter); - - @NonNull - ArrayMap getPOIProjectionMap(); - - @NonNull - String[] getPOIProjection(); - - @NonNull - String getPOITable(); - - @Nullable - Cursor getSearchSuggest(@Nullable String query); - - @Nullable - String getSearchSuggestTable(); - - @Nullable - ArrayMap getSearchSuggestProjectionMap(); - - @SuppressWarnings("unused") - String[] PROJECTION_POI_ALL_COLUMNS = null; // null = return all columns - - String[] PROJECTION_POI = new String[]{ - Columns.T_POI_K_UUID_META, - Columns.T_POI_K_DST_ID_META, - Columns.T_POI_K_ID, - Columns.T_POI_K_NAME, - Columns.T_POI_K_LAT, - Columns.T_POI_K_LNG, - Columns.T_POI_K_ACCESSIBLE, - Columns.T_POI_K_TYPE, - Columns.T_POI_K_STATUS_TYPE, - Columns.T_POI_K_ACTIONS_TYPE, - }; - - String[] PROJECTION_POI_SEARCH_SUGGEST = new String[]{SearchManager.SUGGEST_COLUMN_TEXT_1}; - - class Columns { - public static final String T_POI_K_ID = BaseColumns._ID; - @SuppressWarnings("WeakerAccess") - public static final String T_POI_K_UUID_META = "uuid"; - public static final String T_POI_K_DST_ID_META = "dst"; - public static final String T_POI_K_NAME = "name"; - public static final String T_POI_K_LAT = "lat"; - public static final String T_POI_K_LNG = "lng"; - public static final String T_POI_K_ACCESSIBLE = "a11y"; - public static final String T_POI_K_TYPE = "type"; - public static final String T_POI_K_STATUS_TYPE = "statustype"; - public static final String T_POI_K_ACTIONS_TYPE = "actionstype"; - // - public static final String T_POI_K_SCORE_META_OPT = "score"; // optional - - @SuppressWarnings("unused") - @NonNull - public static String getFkColumnName(@NonNull String key) { - return "fk" + "_" + key; - } - } - - @SuppressWarnings({"WeakerAccess", "unused"}) - class Filter extends ProviderContract.Filter implements MTLog.Loggable { - - private static final String LOG_TAG = POIProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - @Nullable - private Double lat = null; - @Nullable - private Double lng = null; - @Nullable - private Double aroundDiff = null; - - @Nullable - private Double minLat = null; - @Nullable - private Double maxLat = null; - @Nullable - private Double minLng = null; - @Nullable - private Double maxLng = null; - @Nullable - private Double optLoadedMinLat = null; - @Nullable - private Double optLoadedMaxLat = null; - @Nullable - private Double optLoadedMinLng = null; - @Nullable - private Double optLoadedMaxLng = null; - - @Nullable - private Collection uuids = null; - - @NonNull - private final SimpleArrayMap extras = new SimpleArrayMap<>(); - - @Nullable - private String sqlSelection = null; - - @Nullable - private String[] searchKeywords = null; - - private Filter() { - } - - @NonNull - public static Filter getNewEmptyFilter() { - return getNewSqlSelectionFilter(StringUtils.EMPTY); - } - - @NonNull - public static Filter getNewSqlSelectionFilter(@NonNull String sqlSelection) { - return new Filter().setSqlSelection(sqlSelection); - } - - @NonNull - private Filter setSqlSelection(@NonNull String sqlSelection) { - //noinspection ConstantConditions - if (sqlSelection == null) { - throw new UnsupportedOperationException("Need an SQL selection!"); - } - this.sqlSelection = sqlSelection; - return this; - } - - @NonNull - public static Filter getNewSearchFilter(@NonNull String searchKeyword) { - return getNewSearchFilter(new String[]{searchKeyword}); - } - - @NonNull - public static Filter getNewSearchFilter(@NonNull String[] searchKeywords) { - return new Filter().setSearchKeywords(searchKeywords); - } - - @NonNull - private Filter setSearchKeywords(@NonNull String[] searchKeywords) { - if (ArrayUtils.getSize(searchKeywords) == 0) { - throw new UnsupportedOperationException("Need at least 1 search keyword!"); - } - this.searchKeywords = searchKeywords; - return this; - } - - @NonNull - public static Filter getNewUUIDFilter(@NonNull String uuid) { - return getNewUUIDsFilter(Collections.singletonList(uuid)); - } - - @NonNull - public static Filter getNewUUIDsFilter(@NonNull Collection uuids) { - return new Filter().setUUIDs(uuids); - } - - @NonNull - private Filter setUUIDs(@NonNull Collection uuids) { - if (CollectionUtils.getSize(uuids) == 0) { - throw new UnsupportedOperationException("Need at least 1 uuid!"); - } - this.uuids = uuids; - return this; - } - - @NonNull - public static Filter getNewAroundFilter(double lat, double lng, double aroundDiff) { - return new Filter().setAround(lat, lng, aroundDiff); - } - - @NonNull - private Filter setAround(double lat, double lng, double aroundDiff) { - this.lat = lat; - this.lng = lng; - this.aroundDiff = aroundDiff; - return this; - } - - @NonNull - public static Filter getNewAreaFilter(double minLat, double maxLat, double minLng, double maxLng, - @Nullable Double optLoadedMinLat, @Nullable Double optLoadedMaxLat, @Nullable Double optLoadedMinLng, @Nullable Double optLoadedMaxLng) { - return new Filter().setArea( - minLat, maxLat, minLng, maxLng, - optLoadedMinLat, optLoadedMaxLat, optLoadedMinLng, optLoadedMaxLng - ); - } - - @NonNull - private Filter setArea(double minLat, double maxLat, double minLng, double maxLng, - @Nullable Double optLoadedMinLat, @Nullable Double optLoadedMaxLat, @Nullable Double optLoadedMinLng, @Nullable Double optLoadedMaxLng) { - this.minLat = minLat; - this.maxLat = maxLat; - this.minLng = minLng; - this.maxLng = maxLng; - this.optLoadedMinLat = optLoadedMinLat; - this.optLoadedMaxLat = optLoadedMaxLat; - this.optLoadedMinLng = optLoadedMinLng; - this.optLoadedMaxLng = optLoadedMaxLng; - return this; - } - - @NonNull - @Override - public String toString() { - StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()).append('['); - if (isAreaFilter(this)) { - sb.append("lat:").append(this.lat).append(','); - sb.append("lng:").append(this.lng).append(','); - sb.append("aroundDiff:").append(this.aroundDiff).append(','); - } else if (isAreasFilter(this)) { - sb.append("minLat:").append(this.minLat).append(','); - sb.append("maxLat:").append(this.maxLat).append(','); - sb.append("minLng:").append(this.minLng).append(','); - sb.append("maxLng:").append(this.maxLng).append(','); - if (optLoadedMinLat != null) sb.append("optLoadedMinLat:").append(this.optLoadedMinLat).append(','); - if (optLoadedMaxLat != null) sb.append("optLoadedMaxLat:").append(this.optLoadedMaxLat).append(','); - if (optLoadedMinLng != null) sb.append("optLoadedMinLng:").append(this.optLoadedMinLng).append(','); - if (optLoadedMaxLng != null) sb.append("optLoadedMaxLng:").append(this.optLoadedMaxLng).append(','); - } else if (isUUIDFilter(this)) { - sb.append("uuids:").append(this.uuids).append(','); - } else if (isSearchKeywords(this)) { - sb.append("searchKeywords:").append((this.searchKeywords == null ? null : Arrays.asList(this.searchKeywords))).append(','); - } else if (isSQLSelection(this)) { - sb.append("sqlSelection:").append(this.sqlSelection).append(','); - } - sb.append("extras:").append(this.extras).append(','); - sb.append(super.toStringParts()); - sb.append(']'); - return sb.toString(); - } - - public static boolean isUUIDFilter(@Nullable Filter poiFilter) { - return poiFilter != null && CollectionUtils.getSize(poiFilter.uuids) > 0; - } - - public static boolean isAreaFilter(@Nullable Filter poiFilter) { - return poiFilter != null && poiFilter.lat != null && poiFilter.lng != null && poiFilter.aroundDiff != null; - } - - public static boolean isAreasFilter(@Nullable Filter poiFilter) { - return poiFilter != null && poiFilter.minLat != null && poiFilter.maxLat != null && poiFilter.minLng != null && poiFilter.maxLng != null; - } - - public static boolean isSearchKeywords(@Nullable Filter poiFilter) { - return poiFilter != null && ArrayUtils.getSize(poiFilter.searchKeywords) > 0; - } - - public static boolean isSQLSelection(@Nullable Filter poiFilter) { - return poiFilter != null && poiFilter.sqlSelection != null; - } - - public void addExtra(@NonNull String key, @NonNull Object value) { - // TODO CRASH SimpleArrayMap ClassCastException: String cannot be cast to Object[] - this.extras.put(key, value); - } - - @Nullable - public Double getLat() { - return lat; - } - - @Nullable - public Double getLng() { - return lng; - } - - @Nullable - public Double getAroundDiff() { - return aroundDiff; - } - - @Nullable - public String getSqlSelection(@NonNull String uuidTableColumn, @NonNull String latTableColumn, - @NonNull String lngTableColumn, @NonNull String[] searchableLikeColumns, - @NonNull String[] searchableEqualColumns) { - if (isAreaFilter(this) && this.lat != null && this.lng != null && this.aroundDiff != null) { - return LocationUtils.genAroundWhere(this.lat, this.lng, latTableColumn, lngTableColumn, this.aroundDiff); - } else if (isAreasFilter(this)) { - StringBuilder sb = new StringBuilder(); - if (this.minLat != null && this.maxLat != null && this.minLng != null && this.maxLng != null) { - sb.append(SqlUtils.P1); - sb.append(SqlUtils.getBetween(latTableColumn, this.minLat, this.maxLat)); - sb.append(SqlUtils.AND); - sb.append(SqlUtils.getBetween(lngTableColumn, this.minLng, this.maxLng)); - sb.append(SqlUtils.P2); - } - if (this.optLoadedMinLat != null && this.optLoadedMaxLat != null && this.optLoadedMinLng != null && this.optLoadedMaxLng != null) { - if (sb.length() > 0) { - sb.append(SqlUtils.AND); - } - sb.append(SqlUtils.NOT); - sb.append(SqlUtils.P1); - sb.append(SqlUtils.getBetween(latTableColumn, this.optLoadedMinLat, this.optLoadedMaxLat)); - sb.append(SqlUtils.AND); - sb.append(SqlUtils.getBetween(lngTableColumn, this.optLoadedMinLng, this.optLoadedMaxLng)); - sb.append(SqlUtils.P2); - } - return sb.toString(); - } else if (isUUIDFilter(this)) { - return SqlUtils.getWhereInString(uuidTableColumn, this.uuids); - } else if (isSearchKeywords(this) && searchKeywords != null) { - return getSearchSelection(this.searchKeywords, searchableLikeColumns, searchableEqualColumns); - } else if (isSQLSelection(this)) { - return this.sqlSelection; - } else { - throw new UnsupportedOperationException("SQL selection impossible!"); - } - } - - @Nullable - public String[] getSearchKeywords() { - return searchKeywords; - } - - @NonNull - public static String getSearchSelection(@NonNull String[] searchKeywords, @Nullable String[] searchableLikeColumns, @Nullable String[] searchableEqualColumns) { - if (ArrayUtils.getSize(searchKeywords) == 0 - || TextUtils.isEmpty(searchKeywords[0])) { - throw new UnsupportedOperationException( - String.format("SQL search selection needs at least 1 keyword (%s)!", ArrayUtils.getSize(searchKeywords))); - } - if (ArrayUtils.getSize(searchableLikeColumns) == 0 - && ArrayUtils.getSize(searchableEqualColumns) == 0) { - throw new UnsupportedOperationException(String.format("SQL search selection needs at least 1 searchable columns (%s|%s)!", - ArrayUtils.getSize(searchableLikeColumns), ArrayUtils.getSize(searchableEqualColumns))); - } - StringBuilder selectionSb = new StringBuilder(); - for (String searchKeyword : searchKeywords) { - if (TextUtils.isEmpty(searchKeyword)) { - continue; - } - String[] keywords = searchKeyword.toLowerCase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON); - for (String keyword : keywords) { - if (TextUtils.isEmpty(keyword)) { - continue; - } - if (selectionSb.length() > 0) { - selectionSb.append(SqlUtils.AND); - } - selectionSb.append(SqlUtils.P1); - int c = 0; - c = getSearchSelectionLikeColumns(searchableLikeColumns, selectionSb, keyword, c); - //noinspection UnusedAssignment - c = getSearchSelectionEqualColumns(searchableEqualColumns, selectionSb, keyword, c); - selectionSb.append(SqlUtils.P2); - } - } - return selectionSb.toString(); - } - - private static int getSearchSelectionEqualColumns(String[] searchableEqualColumns, @NonNull StringBuilder selectionSb, @NonNull String keyword, int c) { - if (searchableEqualColumns != null) { - for (String searchableColumn : searchableEqualColumns) { - if (TextUtils.isEmpty(searchableColumn)) { - continue; - } - if (c > 0) { - selectionSb.append(SqlUtils.OR); - } - selectionSb.append(SqlUtils.getWhereEqualsString(searchableColumn, keyword)); - c++; - } - } - return c; - } - - private static int getSearchSelectionLikeColumns(@Nullable String[] searchableLikeColumns, @NonNull StringBuilder selectionSb, @NonNull String keyword, int c) { - if (searchableLikeColumns != null) { - for (String searchableColumn : searchableLikeColumns) { - if (TextUtils.isEmpty(searchableColumn)) { - continue; - } - if (c > 0) { - selectionSb.append(SqlUtils.OR); - } - selectionSb.append(SqlUtils.getLikeContains(searchableColumn, keyword)); - c++; - } - } - return c; - } - - @NonNull - public static String getSearchSelectionScore(@NonNull String[] searchKeywords, @Nullable String[] searchableLikeColumns, @Nullable String[] searchableEqualColumns) { - if (ArrayUtils.getSize(searchKeywords) == 0 - || TextUtils.isEmpty(searchKeywords[0])) { - throw new UnsupportedOperationException(String.format("SQL search selection score needs at least 1 keyword (%s)!", - ArrayUtils.getSize(searchKeywords))); - } - if (ArrayUtils.getSize(searchableLikeColumns) == 0 - && ArrayUtils.getSize(searchableEqualColumns) == 0) { - throw new UnsupportedOperationException(String.format("SQL search selection score needs at least 1 searchable columns (%s|%s)!", - ArrayUtils.getSize(searchableLikeColumns), ArrayUtils.getSize(searchableEqualColumns))); - } - StringBuilder selectionSb = new StringBuilder(); - int c = 0; - for (String searchKeyword : searchKeywords) { - if (TextUtils.isEmpty(searchKeyword)) { - continue; - } - String[] keywords = searchKeyword.toLowerCase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON); - for (String keyword : keywords) { - if (TextUtils.isEmpty(searchKeyword)) { - continue; - } - c = getSearchSelectionScoreLikeColumns(searchableLikeColumns, selectionSb, keyword, c); - c = getSearchSelectionScoreEqualColumns(searchableEqualColumns, selectionSb, keyword, c); - } - } - return selectionSb.toString(); - } - - private static final String PLUS = " + "; - - private static int getSearchSelectionScoreEqualColumns(String[] searchableEqualColumns, StringBuilder selectionSb, String keyword, int c) { - if (searchableEqualColumns != null) { - for (String searchableColumn : searchableEqualColumns) { - if (TextUtils.isEmpty(searchableColumn)) { - continue; - } - if (c > 0) { - selectionSb.append(PLUS); - } - selectionSb.append(SqlUtils.P1).append(SqlUtils.getWhereEqualsString(searchableColumn, keyword)).append(SqlUtils.P2).append("*2"); - c++; - } - } - return c; - } - - private static int getSearchSelectionScoreLikeColumns(String[] searchableLikeColumns, StringBuilder selectionSb, String keyword, int c) { - if (searchableLikeColumns != null) { - for (String searchableColumn : searchableLikeColumns) { - if (TextUtils.isEmpty(searchableColumn)) { - continue; - } - if (c > 0) { - selectionSb.append(PLUS); - } - selectionSb.append(SqlUtils.P1).append(SqlUtils.getLikeContains(searchableColumn, keyword)).append(SqlUtils.P2); - c++; - } - } - return c; - } - - @Nullable - public static Filter fromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? null : fromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return null; - } - } - - @SuppressWarnings("ConstantConditions") - private static Filter fromJSON(JSONObject json) { - try { - final Filter poiFilter = new Filter(); - ProviderContract.Filter.fromJSON(poiFilter, json); - Double lat; - Double lng; - Double aroundDiff; - Double minLat; - Double maxLat; - Double minLng; - Double maxLng; - Double optLoadedMinLat; - Double optLoadedMaxLat; - Double optLoadedMinLng; - Double optLoadedMaxLng; - try { - lat = json.getDouble(JSON_LAT); - lng = json.getDouble(JSON_LNG); - aroundDiff = json.getDouble(JSON_AROUND_DIFF); - } catch (JSONException jsone) { - lat = null; - lng = null; - aroundDiff = null; - } - try { - minLat = json.getDouble(JSON_MIN_LAT); - maxLat = json.getDouble(JSON_MAX_LAT); - minLng = json.getDouble(JSON_MIN_LNG); - maxLng = json.getDouble(JSON_MAX_LNG); - optLoadedMinLat = json.has(JSON_OPT_LOADED_MIN_LAT) ? json.getDouble(JSON_OPT_LOADED_MIN_LAT) : null; - optLoadedMaxLat = json.has(JSON_OPT_LOADED_MAX_LAT) ? json.getDouble(JSON_OPT_LOADED_MAX_LAT) : null; - optLoadedMinLng = json.has(JSON_OPT_LOADED_MIN_LNG) ? json.getDouble(JSON_OPT_LOADED_MIN_LNG) : null; - optLoadedMaxLng = json.has(JSON_OPT_LOADED_MAX_LNG) ? json.getDouble(JSON_OPT_LOADED_MAX_LNG) : null; - } catch (JSONException jsone) { - minLat = null; - maxLat = null; - minLng = null; - maxLng = null; - optLoadedMinLat = null; - optLoadedMaxLat = null; - optLoadedMinLng = null; - optLoadedMaxLng = null; - } - JSONArray jUUIDs = json.optJSONArray(JSON_UUIDS); - JSONArray jSearchKeywords = json.optJSONArray(JSON_SEARCH_KEYWORDS); - String sqlSelection = json.optString(JSON_SQL_SELECTION); - if (lat != null && lng != null && aroundDiff != null) { - poiFilter.setAround(lat, lng, aroundDiff); - } else if (minLat != null && maxLat != null && minLng != null && maxLat != null) { - poiFilter.setArea(minLat, maxLat, minLng, maxLng, optLoadedMinLat, optLoadedMaxLat, optLoadedMinLng, optLoadedMaxLng); - } else if (jUUIDs != null && jUUIDs.length() > 0) { - HashSet uuids = new HashSet<>(); - for (int i = 0; i < jUUIDs.length(); i++) { - uuids.add(jUUIDs.getString(i)); - } - poiFilter.setUUIDs(uuids); - } else if (jSearchKeywords != null && jSearchKeywords.length() > 0) { - ArrayList searchKeywords = new ArrayList<>(); - for (int i = 0; i < jSearchKeywords.length(); i++) { - searchKeywords.add(jSearchKeywords.getString(i)); - } - poiFilter.setSearchKeywords(searchKeywords.toArray(new String[0])); - } else if (sqlSelection != null) { - poiFilter.setSqlSelection(sqlSelection); - } else { - MTLog.w(LOG_TAG, "Empty POI filter JSON object '%s'", json); - return null; - } - JSONArray jExtras = json.getJSONArray(JSON_EXTRAS); - for (int i = 0; i < jExtras.length(); i++) { - JSONObject jExtra = jExtras.getJSONObject(i); - String key = jExtra.getString(JSON_EXTRAS_KEY); - Object value = jExtra.get(JSON_EXTRAS_VALUE); - poiFilter.addExtra(key, value); - } - return poiFilter; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); - return null; - } - } - - private static final String JSON_LAT = "lat"; - private static final String JSON_LNG = "lng"; - private static final String JSON_AROUND_DIFF = "aroundDiff"; - private static final String JSON_MIN_LAT = "minLat"; - private static final String JSON_MAX_LAT = "maxLat"; - private static final String JSON_MIN_LNG = "minLng"; - private static final String JSON_MAX_LNG = "maxLng"; - private static final String JSON_OPT_LOADED_MIN_LAT = "optLoadedMinLat"; - private static final String JSON_OPT_LOADED_MAX_LAT = "optLoadedMaxLat"; - private static final String JSON_OPT_LOADED_MIN_LNG = "optLoadedMinLng"; - private static final String JSON_OPT_LOADED_MAX_LNG = "optLoadedMaxLng"; - private static final String JSON_UUIDS = "uuids"; - private static final String JSON_SEARCH_KEYWORDS = "searchKeywords"; - private static final String JSON_SQL_SELECTION = "sqlSelection"; - private static final String JSON_EXTRAS = "extras"; - private static final String JSON_EXTRAS_KEY = "key"; - private static final String JSON_EXTRAS_VALUE = "value"; - - @Nullable - @Override - public String toJSONString() { - return toJSONString(this); - } - - @Nullable - private static String toJSONString(@NonNull Filter statusFilter) { - final JSONObject json = toJSON(statusFilter); - return json == null ? null : json.toString(); - } - - @Nullable - public static JSONObject toJSON(@Nullable Filter poiFilter) { - try { - final JSONObject json = new JSONObject(); - if (isAreaFilter(poiFilter)) { - json.put(JSON_LAT, poiFilter.lat); - json.put(JSON_LNG, poiFilter.lng); - json.put(JSON_AROUND_DIFF, poiFilter.aroundDiff); - } else if (isAreasFilter(poiFilter)) { - json.put(JSON_MIN_LAT, poiFilter.minLat); - json.put(JSON_MAX_LAT, poiFilter.maxLat); - json.put(JSON_MIN_LNG, poiFilter.minLng); - json.put(JSON_MAX_LNG, poiFilter.maxLng); - if (poiFilter.optLoadedMinLat != null) { - json.put(JSON_OPT_LOADED_MIN_LAT, poiFilter.optLoadedMinLat); - } - if (poiFilter.optLoadedMaxLat != null) { - json.put(JSON_OPT_LOADED_MAX_LAT, poiFilter.optLoadedMaxLat); - } - if (poiFilter.optLoadedMinLng != null) { - json.put(JSON_OPT_LOADED_MIN_LNG, poiFilter.optLoadedMinLng); - } - if (poiFilter.optLoadedMaxLng != null) { - json.put(JSON_OPT_LOADED_MAX_LNG, poiFilter.optLoadedMaxLng); - } - } else if (isUUIDFilter(poiFilter) && poiFilter.uuids != null) { - JSONArray jUUIDs = new JSONArray(); - for (String uuid : poiFilter.uuids) { - jUUIDs.put(uuid); - } - json.put(JSON_UUIDS, jUUIDs); - } else if (isSearchKeywords(poiFilter) && poiFilter.searchKeywords != null) { - JSONArray jSearchKeywords = new JSONArray(); - for (String searchKeyword : poiFilter.searchKeywords) { - jSearchKeywords.put(searchKeyword); - } - json.put(JSON_SEARCH_KEYWORDS, jSearchKeywords); - } else if (isSQLSelection(poiFilter)) { - json.put(JSON_SQL_SELECTION, poiFilter.sqlSelection); - } else { - MTLog.w(LOG_TAG, "Empty POI filter '%s' converted to JSON!", poiFilter); - } - final JSONArray jExtras = new JSONArray(); - if (poiFilter != null) { - ProviderContract.Filter.toJSON(poiFilter, json); - for (int i = 0; i < poiFilter.extras.size(); i++) { - JSONObject jExtra = new JSONObject(); - jExtra.put(JSON_EXTRAS_KEY, poiFilter.extras.keyAt(i)); - jExtra.put(JSON_EXTRAS_VALUE, poiFilter.extras.valueAt(i)); - jExtras.put(jExtra); - } - } - json.put(JSON_EXTRAS, jExtras); - return json; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", poiFilter); - return null; - } - } - - public boolean getExtraBoolean(@NonNull String key, boolean defaultValue) { - final Object value = this.extras.get(key); - if (value == null) { - return defaultValue; - } - return (Boolean) value; - } - - @Nullable - public String getExtraString(@NonNull String key, @Nullable String defaultValue) { - final Object value = this.extras.get(key); - if (value == null) { - return defaultValue; - } - return (String) value; - } - - @Nullable - public Double getExtraDouble(@NonNull String key, @Nullable Double defaultValue) { - final Object value = this.extras.get(key); - if (value == null) { - return defaultValue; - } - return (Double) value; - } - } -} diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt new file mode 100644 index 00000000..f24a68f8 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt @@ -0,0 +1,528 @@ +package org.mtransit.android.commons.provider.poi + +import android.annotation.SuppressLint +import android.app.SearchManager +import android.database.Cursor +import android.provider.BaseColumns +import androidx.annotation.Discouraged +import androidx.collection.ArrayMap +import androidx.collection.SimpleArrayMap +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.LocationUtils +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.SqlUtils +import org.mtransit.android.commons.optDouble +import org.mtransit.android.commons.provider.common.ContentProviderConstants +import org.mtransit.android.commons.provider.common.ProviderContract +import java.util.Locale + +interface POIProviderContract : ProviderContract { + + companion object { + const val POI_PATH: String = "poi" + + const val POI_FILTER_EXTRA_AVOID_LOADING: String = "avoidLoading" + + const val POI_FILTER_EXTRA_SORT_ORDER: String = "sortOrder" + + @JvmStatic + val PROJECTION_POI_ALL_COLUMNS: Array? = null // null = return all columns + + @JvmStatic + val PROJECTION_POI = arrayOf( + Columns.T_POI_K_UUID_META, + Columns.T_POI_K_DST_ID_META, + Columns.T_POI_K_ID, + Columns.T_POI_K_NAME, + Columns.T_POI_K_LAT, + Columns.T_POI_K_LNG, + Columns.T_POI_K_ACCESSIBLE, + Columns.T_POI_K_TYPE, + Columns.T_POI_K_STATUS_TYPE, + Columns.T_POI_K_ACTIONS_TYPE, + ) + + @JvmStatic + val PROJECTION_POI_SEARCH_SUGGEST = arrayOf(SearchManager.SUGGEST_COLUMN_TEXT_1) + } + + val poiMaxValidityInMs: Long + + val poiValidityInMs: Long + + fun getPOI(poiFilter: Filter?): Cursor? + + fun getPOIFromDB(poiFilter: Filter?): Cursor? + + val poiProjectionMap: ArrayMap + + val poiProjection: Array + + val pOITable: String + + fun getSearchSuggest(query: String?): Cursor? + + val searchSuggestTable: String? + + val searchSuggestProjectionMap: ArrayMap? + + object Columns { + const val T_POI_K_ID: String = BaseColumns._ID + const val T_POI_K_UUID_META: String = "uuid" + const val T_POI_K_DST_ID_META: String = "dst" + const val T_POI_K_NAME: String = "name" + const val T_POI_K_LAT: String = "lat" + const val T_POI_K_LNG: String = "lng" + const val T_POI_K_ACCESSIBLE: String = "a11y" + const val T_POI_K_TYPE: String = "type" + const val T_POI_K_STATUS_TYPE: String = "statustype" + const val T_POI_K_ACTIONS_TYPE: String = "actionstype" + + const val T_POI_K_SCORE_META_OPT: String = "score" // optional + + @JvmStatic + @Suppress("unused") + fun getFkColumnName(key: String) = "fk_$key" + } + + @Suppress("unused") + data class Filter @Discouraged("use static methods instead") constructor( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, + val lat: Double? = null, + val lng: Double? = null, + val aroundDiff: Double? = null, + private val minLat: Double? = null, + private val maxLat: Double? = null, + private val minLng: Double? = null, + private val maxLng: Double? = null, + private val optLoadedMinLat: Double? = null, + private val optLoadedMaxLat: Double? = null, + private val optLoadedMinLng: Double? = null, + private val optLoadedMaxLng: Double? = null, + private val uuids: Collection? = null, + private val extras: SimpleArrayMap = SimpleArrayMap(), + private val sqlSelection: String? = null, + val searchKeywords: List? = null, + ) : ProviderContract.Filter(), MTLog.Loggable { + + companion object { + private val LOG_TAG = POIProviderContract::class.java.getSimpleName() + ">" + Filter::class.java.getSimpleName() + + @SuppressLint("DiscouragedApi") + fun getNewEmptyFilter() = Filter() + + @SuppressLint("DiscouragedApi") + fun getNewSqlSelectionFilter(sqlSelection: String) = Filter(sqlSelection = sqlSelection) + + fun getNewSearchFilter(searchKeyword: String) = getNewSearchFilter(listOf(searchKeyword)) + + @SuppressLint("DiscouragedApi") + fun getNewSearchFilter(searchKeywords: List) = Filter(searchKeywords = searchKeywords) + + fun getNewUUIDFilter(uuid: String): Filter { + return getNewUUIDsFilter(listOf(uuid)) + } + + @SuppressLint("DiscouragedApi") + fun getNewUUIDsFilter(uuids: Collection) = Filter(uuids = uuids) + + @SuppressLint("DiscouragedApi") + fun getNewAroundFilter(lat: Double, lng: Double, aroundDiff: Double) = Filter(lat = lat, lng = lng, aroundDiff = aroundDiff) + + @SuppressLint("DiscouragedApi") + fun getNewAreaFilter( + minLat: Double, maxLat: Double, minLng: Double, maxLng: Double, + optLoadedMinLat: Double?, optLoadedMaxLat: Double?, optLoadedMinLng: Double?, optLoadedMaxLng: Double? + ) = Filter( + minLat = minLat, maxLat = maxLat, minLng = minLng, maxLng = maxLng, + optLoadedMinLat = optLoadedMinLat, optLoadedMaxLat = optLoadedMaxLat, optLoadedMinLng = optLoadedMinLng, optLoadedMaxLng = optLoadedMaxLng + ) + + fun isUUIDFilter(poiFilter: Filter?) = poiFilter?.uuids?.isNotEmpty() == true + + fun isAreaFilter(poiFilter: Filter?) = poiFilter?.let { it.lat != null && it.lng != null && it.aroundDiff != null } == true + + fun isAreasFilter(poiFilter: Filter?) = poiFilter?.let { it.minLat != null && it.maxLat != null && it.minLng != null && it.maxLng != null } == true + + @JvmStatic + fun isSearchKeywords(poiFilter: Filter?) = poiFilter?.searchKeywords?.isNotEmpty() == true + + fun isSQLSelection(poiFilter: Filter?) = poiFilter?.sqlSelection != null + + @JvmStatic + fun getSearchSelection(searchKeyword: String?, searchableLikeColumns: Array?, searchableEqualColumns: Array?) = + getSearchSelection(searchKeyword?.let { listOf(searchKeyword) }, searchableLikeColumns, searchableEqualColumns) + + @JvmStatic + fun getSearchSelection(searchKeywords: List?, searchableLikeColumns: Array?, searchableEqualColumns: Array?) = buildString { + if (searchKeywords.isNullOrEmpty() || searchKeywords[0].isEmpty()) { + throw UnsupportedOperationException("SQL search selection needs at least 1 keyword ($searchKeywords.size)!") + } + if (searchableLikeColumns?.isNotEmpty() != true && searchableEqualColumns?.isNotEmpty() != true) { + throw UnsupportedOperationException("SQL search selection needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") + } + for (searchKeyword in searchKeywords) { + if (searchKeyword.isEmpty()) continue + val keywords = + searchKeyword.lowercase().split(ContentProviderConstants.SEARCH_SPLIT_ON.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (keyword in keywords) { + if (keyword.isEmpty()) continue + if (isNotEmpty()) append(SqlUtils.AND) + append(SqlUtils.P1) + var c = 0 + c = getSearchSelectionLikeColumns(searchableLikeColumns, keyword, c) + @Suppress("AssignedValueIsNeverRead") + c = getSearchSelectionEqualColumns(searchableEqualColumns, keyword, c) + append(SqlUtils.P2) + } + } + } + + private fun StringBuilder.getSearchSelectionEqualColumns(searchableEqualColumns: Array?, keyword: String, c: Int): Int { + var c = c + searchableEqualColumns?.forEach { searchableColumn -> + if (searchableColumn.isEmpty()) return@forEach + if (c > 0) append(SqlUtils.OR) + append(SqlUtils.getWhereEqualsString(searchableColumn, keyword)) + c++ + } + return c + } + + private fun StringBuilder.getSearchSelectionLikeColumns(searchableLikeColumns: Array?, keyword: String, c: Int): Int { + var c = c + searchableLikeColumns?.forEach { searchableColumn -> + if (searchableColumn.isEmpty()) return@forEach + if (c > 0) append(SqlUtils.OR) + append(SqlUtils.getLikeContains(searchableColumn, keyword)) + c++ + } + return c + } + + @JvmStatic + fun getSearchSelectionScore(searchKeywords: List?, searchableLikeColumns: Array?, searchableEqualColumns: Array?) = + buildString { + if (searchKeywords.isNullOrEmpty() || searchKeywords[0].isEmpty()) { + throw UnsupportedOperationException("SQL search selection score needs at least 1 keyword (${searchKeywords?.size})!") + } + if (searchableLikeColumns?.isNotEmpty() != true && searchableEqualColumns?.isNotEmpty() != true) { + throw UnsupportedOperationException("SQL search selection score needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") + } + var c = 0 + for (searchKeyword in searchKeywords) { + if (searchKeyword.isEmpty()) continue + val keywords = searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON.toRegex()) + for (keyword in keywords) { + if (searchKeyword.isEmpty()) continue + c = getSearchSelectionScoreLikeColumns(searchableLikeColumns, keyword, c) + c = getSearchSelectionScoreEqualColumns(searchableEqualColumns, keyword, c) + } + } + } + + private const val PLUS = " + " + + private fun StringBuilder.getSearchSelectionScoreEqualColumns(searchableEqualColumns: Array?, keyword: String, c: Int): Int { + var c = c + searchableEqualColumns?.forEach { searchableColumn -> + if (searchableColumn.isEmpty()) return@forEach + if (c > 0) append(PLUS) + append(SqlUtils.P1).append(SqlUtils.getWhereEqualsString(searchableColumn, keyword)).append(SqlUtils.P2).append("*2") + c++ + } + return c + } + + private fun StringBuilder.getSearchSelectionScoreLikeColumns(searchableLikeColumns: Array?, keyword: String, c: Int): Int { + var c = c + searchableLikeColumns?.forEach { searchableColumn -> + if (searchableColumn.isEmpty()) return@forEach + if (c > 0) append(PLUS) + append(SqlUtils.P1).append(SqlUtils.getLikeContains(searchableColumn, keyword)).append(SqlUtils.P2) + c++ + } + return c + } + + @JvmStatic + fun fromJSONString(jsonString: String?): Filter? { + try { + return jsonString?.let { fromJSON(JSONObject(it)) } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '$jsonString'") + return null + } + } + + private fun fromJSON(json: JSONObject): Filter? { + try { + var lat: Double? + var lng: Double? + var aroundDiff: Double? + var minLat: Double? + var maxLat: Double? + var minLng: Double? + var maxLng: Double? + var optLoadedMinLat: Double? + var optLoadedMaxLat: Double? + var optLoadedMinLng: Double? + var optLoadedMaxLng: Double? + try { + lat = json.getDouble(JSON_LAT) + lng = json.getDouble(JSON_LNG) + aroundDiff = json.getDouble(JSON_AROUND_DIFF) + } catch (jsone: JSONException) { + lat = null + lng = null + aroundDiff = null + } + try { + minLat = json.getDouble(JSON_MIN_LAT) + maxLat = json.getDouble(JSON_MAX_LAT) + minLng = json.getDouble(JSON_MIN_LNG) + maxLng = json.getDouble(JSON_MAX_LNG) + optLoadedMinLat = json.optDouble(JSON_OPT_LOADED_MIN_LAT, null) + optLoadedMaxLat = json.optDouble(JSON_OPT_LOADED_MAX_LAT, null) + optLoadedMinLng = json.optDouble(JSON_OPT_LOADED_MIN_LNG, null) + optLoadedMaxLng = json.optDouble(JSON_OPT_LOADED_MAX_LNG, null) + } catch (jsone: JSONException) { + minLat = null + maxLat = null + minLng = null + maxLng = null + optLoadedMinLat = null + optLoadedMaxLat = null + optLoadedMinLng = null + optLoadedMaxLng = null + } + //noinspection DiscouragedApi + return Filter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + lat = lat, + lng = lng, + aroundDiff = aroundDiff, + minLat = minLat, + maxLat = maxLat, + minLng = minLng, + maxLng = maxLng, + optLoadedMinLat = optLoadedMinLat, + optLoadedMaxLat = optLoadedMaxLat, + optLoadedMinLng = optLoadedMinLng, + optLoadedMaxLng = optLoadedMaxLng, + uuids = json.optJSONArray(JSON_UUIDS)?.let { jUUIDs -> + buildList { + for (i in 0.. + val extras = SimpleArrayMap() + for (i in 0.. + buildList { + for (i in 0.., + searchableEqualColumns: Array + ): String? { + if (isAreaFilter(this) && this.lat != null && this.lng != null && this.aroundDiff != null) { + return LocationUtils.genAroundWhere(this.lat, this.lng, latTableColumn, lngTableColumn, this.aroundDiff) + } else if (isAreasFilter(this)) { + val sb = StringBuilder() + if (this.minLat != null && this.maxLat != null && this.minLng != null && this.maxLng != null) { + sb.append(SqlUtils.P1) + sb.append(SqlUtils.getBetween(latTableColumn, this.minLat, this.maxLat)) + sb.append(SqlUtils.AND) + sb.append(SqlUtils.getBetween(lngTableColumn, this.minLng, this.maxLng)) + sb.append(SqlUtils.P2) + } + if (this.optLoadedMinLat != null && this.optLoadedMaxLat != null && this.optLoadedMinLng != null && this.optLoadedMaxLng != null) { + if (sb.isNotEmpty()) sb.append(SqlUtils.AND) + sb.append(SqlUtils.NOT) + sb.append(SqlUtils.P1) + sb.append(SqlUtils.getBetween(latTableColumn, this.optLoadedMinLat, this.optLoadedMaxLat)) + sb.append(SqlUtils.AND) + sb.append(SqlUtils.getBetween(lngTableColumn, this.optLoadedMinLng, this.optLoadedMaxLng)) + sb.append(SqlUtils.P2) + } + return sb.toString() + } else if (isUUIDFilter(this)) { + return SqlUtils.getWhereInString(uuidTableColumn, this.uuids) + } else if (isSearchKeywords(this) && searchKeywords != null) { + return getSearchSelection(this.searchKeywords, searchableLikeColumns, searchableEqualColumns) + } else if (isSQLSelection(this)) { + return this.sqlSelection + } else { + throw UnsupportedOperationException("SQL selection impossible!") + } + } + + override fun toJSONString() = toJSONString(this) + + fun getExtraBoolean(key: String, defaultValue: Boolean): Boolean { + val value = this.extras.get(key) ?: return defaultValue + return value as Boolean + } + + fun getExtraString(key: String, defaultValue: String?): String? { + val value = this.extras.get(key) ?: return defaultValue + return value as String + } + + fun getExtraDouble(key: String, defaultValue: Double?): Double? { + val value = this.extras.get(key) ?: return defaultValue + return value as Double + } + } +} diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java index d7eb9c9b..8c093e52 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProvider.java @@ -153,7 +153,7 @@ private static Cursor getServiceUpdateCursor(@Nullable ServiceUpdates serviceUpd if (serviceUpdates == null) { return ContentProviderConstants.EMPTY_CURSOR; } - MatrixCursor matrixCursor = new MatrixCursor(ServiceUpdateProviderContract.PROJECTION_SERVICE_UPDATE); + MatrixCursor matrixCursor = new MatrixCursor(ServiceUpdateProviderContract.getPROJECTION_SERVICE_UPDATE()); for (ServiceUpdate serviceUpdate : serviceUpdates) { matrixCursor.addRow(serviceUpdate.getCursorRow()); } 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 deleted file mode 100644 index 8cc599c7..00000000 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.java +++ /dev/null @@ -1,324 +0,0 @@ -package org.mtransit.android.commons.provider.serviceupdate; - -import android.net.Uri; -import android.provider.BaseColumns; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; -import org.mtransit.android.commons.JSONUtils; -import org.mtransit.android.commons.MTLog; -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; -import org.mtransit.android.commons.data.RouteDirectionStop; -import org.mtransit.android.commons.data.ServiceUpdates; -import org.mtransit.android.commons.data.Targetable; -import org.mtransit.android.commons.provider.common.ProviderContract; -import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter; - -public interface ServiceUpdateProviderContract extends ProviderContract { - - String SERVICE_UPDATE_PATH = "service"; - - @NonNull - String getAuthority(); - - @NonNull - Uri getAuthorityUri(); - - long getServiceUpdateMaxValidityInMs(); - - long getServiceUpdateValidityInMs(boolean inFocus); - - long getMinDurationBetweenServiceUpdateRefreshInMs(boolean inFocus); - - void cacheServiceUpdates(@NonNull ServiceUpdates newServiceUpdates); - - @Nullable - ServiceUpdates getCachedServiceUpdates(@NonNull Filter serviceUpdateFilter); - - @Nullable - ServiceUpdates getNewServiceUpdates(@NonNull Filter serviceUpdateFilter); - - @SuppressWarnings("UnusedReturnValue") - boolean deleteCachedServiceUpdate(@NonNull Integer serviceUpdateId); - - @SuppressWarnings("UnusedReturnValue") - boolean deleteCachedServiceUpdate(@NonNull String targetUUID, @NonNull String sourceId); - - @SuppressWarnings("UnusedReturnValue") - boolean purgeUselessCachedServiceUpdates(); - - @NonNull - String getServiceUpdateDbTableName(); - - @NonNull - String getServiceUpdateLanguage(); - - String[] PROJECTION_SERVICE_UPDATE = new String[]{ - Columns.T_SERVICE_UPDATE_K_ID, - Columns.T_SERVICE_UPDATE_K_TARGET_UUID, - Columns.T_SERVICE_UPDATE_K_TARGET_TRIP_ID, - Columns.T_SERVICE_UPDATE_K_LAST_UPDATE, - Columns.T_SERVICE_UPDATE_K_MAX_VALIDITY_IN_MS, - Columns.T_SERVICE_UPDATE_K_SEVERITY, - Columns.T_SERVICE_UPDATE_K_TEXT, - Columns.T_SERVICE_UPDATE_K_TEXT_HTML, - Columns.T_SERVICE_UPDATE_K_LANGUAGE, - Columns.T_SERVICE_UPDATE_K_ORIGINAL_ID, - Columns.T_SERVICE_UPDATE_K_SOURCE_LABEL, - Columns.T_SERVICE_UPDATE_K_SOURCE_ID, - Columns.T_SERVICE_UPDATE_K_NO_SERVICE - }; - - class Columns { - public static final String T_SERVICE_UPDATE_K_ID = BaseColumns._ID; - public static final String T_SERVICE_UPDATE_K_TARGET_UUID = "target"; - public static final String T_SERVICE_UPDATE_K_TARGET_TRIP_ID = "trip_id"; - public static final String T_SERVICE_UPDATE_K_LAST_UPDATE = "last_update"; - public static final String T_SERVICE_UPDATE_K_MAX_VALIDITY_IN_MS = "max_validity"; - public static final String T_SERVICE_UPDATE_K_SEVERITY = "severity"; - public static final String T_SERVICE_UPDATE_K_TEXT = "text"; - public static final String T_SERVICE_UPDATE_K_TEXT_HTML = "text_html"; - public static final String T_SERVICE_UPDATE_K_LANGUAGE = "lang"; - public static final String T_SERVICE_UPDATE_K_SOURCE_LABEL = "source_label"; - public static final String T_SERVICE_UPDATE_K_ORIGINAL_ID = "original_id"; - public static final String T_SERVICE_UPDATE_K_SOURCE_ID = "source_id"; - public static final String T_SERVICE_UPDATE_K_NO_SERVICE = "no_service"; - } - - @SuppressWarnings("WeakerAccess") - class Filter extends ProviderContract.Filter implements GTFSRealTimeProviderFilter, MTLog.Loggable { - - private static final String LOG_TAG = ServiceUpdateProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - @Nullable - private final POI poi; // RouteDirectionStop or DefaultPOI - @Nullable - private final String authority; - @Nullable - private final Route route; - @Nullable - private final RouteDirection routeDirection; - - public Filter(@NonNull POI poi) { - this.poi = poi; - this.authority = poi.getAuthority(); - this.route = null; - this.routeDirection = null; - } - - public Filter(@NonNull String authority, @NonNull Route route) { - this.authority = authority; - this.route = route; - this.routeDirection = null; - this.poi = null; - } - - public Filter(@NonNull String authority, @NonNull RouteDirection routeDirection) { - this.authority = authority; - this.routeDirection = routeDirection; - this.route = null; - this.poi = null; - } - - @NonNull - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(Filter.class.getSimpleName()); - sb.append(super.toStringParts()); - if (this.poi != null) { - sb.append("poi:").append(this.poi).append(','); - } - if (this.authority != null) { - sb.append("authority:").append(this.authority).append(','); - } - if (this.route != null) { - sb.append("route:").append(this.route).append(','); - } - if (this.routeDirection != null) { - sb.append("routeDirection:").append(this.routeDirection).append(','); - } - return sb.toString(); - } - - @Nullable - public Targetable getTarget() { - if (this.poi != null) { - return this.poi; - } - if (this.route != null) { - return this.route; - } - //noinspection RedundantIfStatement - if (this.routeDirection != null) { - return this.routeDirection; - } - return null; - } - - @Nullable - public String getTargetUUID() { - final Targetable target = getTarget(); - return target == null ? null : target.getUUID(); - } - - @Nullable - public String getTargetAuthority() { - if (this.poi != null) { - return this.poi.getAuthority(); - } - if (this.route != null) { - return this.route.getAuthority(); - } - if (this.routeDirection != null) { - return this.routeDirection.getAuthority(); - } - return null; - } - - @Nullable - public Route getTargetRoute() { - if (this.poi != null && this.poi instanceof RouteDirectionStop) { - return ((RouteDirectionStop) this.poi).getRoute(); - } - if (this.route != null) { - return this.route; - } - if (this.routeDirection != null) { - return this.routeDirection.getRoute(); - } - return null; - } - - @Nullable - 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(); - } - if (this.routeDirection != null) { - 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() { - return poi; - } - - @Nullable - @Override - public Route getRoute() { - return route; - } - - @Nullable - @Override - public RouteDirection getRouteDirection() { - return routeDirection; - } - - @Nullable - public static Filter fromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? null : fromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return null; - } - } - - private static final String JSON_POI = "poi"; - private static final String JSON_ROUTE = "route"; - private static final String JSON_ROUTE_DIRECTION = "routeDirection"; - private static final String JSON_AUTHORITY = "authority"; - - @Nullable - public static Filter fromJSON(@NonNull JSONObject json) { - try { - final POI poi = json.has(JSON_POI) ? DefaultPOI.fromJSONStatic(json.getJSONObject(JSON_POI)) : null; - final String authority = JSONUtils.optString(json, JSON_AUTHORITY); - final Route route = json.has(JSON_ROUTE) && authority != null ? - Route.fromJSON(json.getJSONObject(JSON_ROUTE), authority) : null; - final RouteDirection routeDirection = json.has(JSON_ROUTE_DIRECTION) && authority != null ? - RouteDirection.fromJSON(json.getJSONObject(JSON_ROUTE_DIRECTION), authority) : null; - final Filter serviceUpdateFilter; - if (poi != null) { - serviceUpdateFilter = new Filter(poi); - } else if (route != null) { - serviceUpdateFilter = new Filter(authority, route); - } else if (routeDirection != null) { - serviceUpdateFilter = new Filter(authority, routeDirection); - } else { - return null; // WTF? - } - ProviderContract.Filter.fromJSON(serviceUpdateFilter, json); - return serviceUpdateFilter; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '%s'", json); - return null; - } - } - - @Nullable - @Override - public String toJSONString() { - return toJSONString(this); - } - - @Nullable - public static String toJSONString(@NonNull Filter serviceUpdateFilter) { - final JSONObject json = toJSON(serviceUpdateFilter); - return json == null ? null : json.toString(); - } - - @Nullable - public static JSONObject toJSON(@NonNull Filter serviceUpdateFilter) { - try { - final JSONObject json = new JSONObject(); - ProviderContract.Filter.toJSON(serviceUpdateFilter, json); - if (serviceUpdateFilter.poi != null) { - json.put(JSON_POI, serviceUpdateFilter.poi.toJSON()); - } - if (serviceUpdateFilter.route != null) { - json.put(JSON_ROUTE, Route.toJSON(serviceUpdateFilter.route)); - } - if (serviceUpdateFilter.routeDirection != null) { - json.put(JSON_ROUTE_DIRECTION, RouteDirection.toJSON(serviceUpdateFilter.routeDirection)); - } - if (serviceUpdateFilter.authority != null) { - json.put(JSON_AUTHORITY, serviceUpdateFilter.authority); - } - return json; - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while making JSON object '%s'", serviceUpdateFilter); - return null; - } - } - } -} diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt new file mode 100644 index 00000000..6e4894be --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt @@ -0,0 +1,222 @@ +package org.mtransit.android.commons.provider.serviceupdate + +import android.annotation.SuppressLint +import android.net.Uri +import android.provider.BaseColumns +import androidx.annotation.Discouraged +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.MTLog +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 +import org.mtransit.android.commons.data.RouteDirectionStop +import org.mtransit.android.commons.data.ServiceUpdates +import org.mtransit.android.commons.data.Targetable +import org.mtransit.android.commons.optString +import org.mtransit.android.commons.provider.common.ProviderContract +import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter + +interface ServiceUpdateProviderContract : ProviderContract { + + companion object { + const val SERVICE_UPDATE_PATH = "service" + + @JvmStatic + val PROJECTION_SERVICE_UPDATE = arrayOf( + Columns.T_SERVICE_UPDATE_K_ID, + Columns.T_SERVICE_UPDATE_K_TARGET_UUID, + Columns.T_SERVICE_UPDATE_K_TARGET_TRIP_ID, + Columns.T_SERVICE_UPDATE_K_LAST_UPDATE, + Columns.T_SERVICE_UPDATE_K_MAX_VALIDITY_IN_MS, + Columns.T_SERVICE_UPDATE_K_SEVERITY, + Columns.T_SERVICE_UPDATE_K_TEXT, + Columns.T_SERVICE_UPDATE_K_TEXT_HTML, + Columns.T_SERVICE_UPDATE_K_LANGUAGE, + Columns.T_SERVICE_UPDATE_K_ORIGINAL_ID, + Columns.T_SERVICE_UPDATE_K_SOURCE_LABEL, + Columns.T_SERVICE_UPDATE_K_SOURCE_ID, + Columns.T_SERVICE_UPDATE_K_NO_SERVICE + ) + } + + val authority: String + + val authorityUri: Uri + + val serviceUpdateMaxValidityInMs: Long + + fun getServiceUpdateValidityInMs(inFocus: Boolean): Long + + fun getMinDurationBetweenServiceUpdateRefreshInMs(inFocus: Boolean): Long + + fun cacheServiceUpdates(newServiceUpdates: ServiceUpdates) + + fun getCachedServiceUpdates(serviceUpdateFilter: Filter): ServiceUpdates? + + fun getNewServiceUpdates(serviceUpdateFilter: Filter): ServiceUpdates? + + fun deleteCachedServiceUpdate(serviceUpdateId: Int): Boolean + + fun deleteCachedServiceUpdate(targetUUID: String, sourceId: String): Boolean + + fun purgeUselessCachedServiceUpdates(): Boolean + + val serviceUpdateDbTableName: String + + val serviceUpdateLanguage: String + + object Columns { + const val T_SERVICE_UPDATE_K_ID: String = BaseColumns._ID + const val T_SERVICE_UPDATE_K_TARGET_UUID = "target" + const val T_SERVICE_UPDATE_K_TARGET_TRIP_ID = "trip_id" + const val T_SERVICE_UPDATE_K_LAST_UPDATE = "last_update" + const val T_SERVICE_UPDATE_K_MAX_VALIDITY_IN_MS = "max_validity" + const val T_SERVICE_UPDATE_K_SEVERITY = "severity" + const val T_SERVICE_UPDATE_K_TEXT = "text" + const val T_SERVICE_UPDATE_K_TEXT_HTML = "text_html" + const val T_SERVICE_UPDATE_K_LANGUAGE = "lang" + const val T_SERVICE_UPDATE_K_SOURCE_LABEL = "source_label" + const val T_SERVICE_UPDATE_K_ORIGINAL_ID = "original_id" + const val T_SERVICE_UPDATE_K_SOURCE_ID = "source_id" + const val T_SERVICE_UPDATE_K_NO_SERVICE = "no_service" + } + + data class Filter @Discouraged("use static methods instead") constructor( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, + val authority: String?, + override val poi: POI? = null, // RouteDirectionStop or DefaultPOI + override val route: Route? = null, + override val routeDirection: RouteDirection? = null, + ) : ProviderContract.Filter(), GTFSRealTimeProviderFilter, MTLog.Loggable { + + companion object { + private val LOG_TAG = ServiceUpdateProviderContract::class.java.getSimpleName() + ">" + Filter::class.java.getSimpleName() + + @JvmOverloads + @JvmStatic + fun from(poi: POI, inFocus: Boolean? = null) = Filter(poi).copy(inFocus = inFocus) + + @JvmStatic + fun fromJSONString(jsonString: String?): Filter? { + try { + return jsonString?.let { fromJSON(JSONObject(it)) } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString) + return null + } + } + + private const val JSON_POI = "poi" + private const val JSON_ROUTE = "route" + private const val JSON_ROUTE_DIRECTION = "routeDirection" + private const val JSON_AUTHORITY = "authority" + + fun fromJSON(json: JSONObject): Filter? { + try { + val poi = json.optJSONObject(JSON_POI)?.let { DefaultPOI.fromJSONStatic(it) } + val authority = json.optString(JSON_AUTHORITY, fallback = null) + val route = authority?.let { authority -> + json.optJSONObject(JSON_ROUTE)?.let { Route.fromJSON(it, authority) } + } + val routeDirection = authority?.let { authority -> + json.optJSONObject(JSON_ROUTE_DIRECTION)?.let { RouteDirection.fromJSON(it, authority) } + } + //noinspection DiscouragedApi + return Filter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + authority = authority, + poi = poi, + route = route, + routeDirection = routeDirection, + ).takeIf { + (it.poi != null) || (it.route != null) || (it.routeDirection != null) + } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON object '$json'") + return null + } + } + + fun toJSONString(serviceUpdateFilter: Filter) = toJSON(serviceUpdateFilter)?.toString() + + fun toJSON(serviceUpdateFilter: Filter): JSONObject? { + try { + return JSONObject().apply { + toJSON(serviceUpdateFilter, this) + if (serviceUpdateFilter.poi != null) { + put(JSON_POI, serviceUpdateFilter.poi.toJSON()) + } + if (serviceUpdateFilter.route != null) { + put(JSON_ROUTE, Route.toJSON(serviceUpdateFilter.route)) + } + if (serviceUpdateFilter.routeDirection != null) { + put(JSON_ROUTE_DIRECTION, RouteDirection.toJSON(serviceUpdateFilter.routeDirection)) + } + if (serviceUpdateFilter.authority != null) { + put(JSON_AUTHORITY, serviceUpdateFilter.authority) + } + } + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while making JSON object '$serviceUpdateFilter'") + return null + } + } + } + + override fun getLogTag() = LOG_TAG + + @SuppressLint("DiscouragedApi") + constructor(poi: POI) : this( + authority = poi.getAuthority(), + poi = poi, + ) + + @SuppressLint("DiscouragedApi") + constructor(authority: String, route: Route) : this( + authority = authority, + route = route, + poi = null, + ) + + @SuppressLint("DiscouragedApi") + constructor(authority: String, routeDirection: RouteDirection) : this( + authority = authority, + routeDirection = routeDirection, + poi = null, + ) + + override fun toString() = buildString { + append(Filter::class.java.getSimpleName()) + append(super.toStringParts()) + authority?.let { append("authority:").append(it).append(',') } + poi?.let { append("poi:").append(it).append(',') } + route?.let { append("route:").append(it).append(',') } + routeDirection?.let { append("routeDirection:").append(it).append(',') } + } + + val target: Targetable? get() = this.poi ?: this.route ?: this.routeDirection + + val targetUUID: String? get() = this.target?.uUID + + val targetAuthority: String? get() = this.poi?.authority ?: this.route?.authority ?: this.routeDirection?.authority + + val targetRoute: Route? get() = (this.poi as? RouteDirectionStop)?.route ?: this.route ?: this.routeDirection?.route + + val targetRouteId: Long? get() = this.targetRoute?.id + + val targetDirection: Direction? get() = (this.poi as? RouteDirectionStop)?.direction ?: this.routeDirection?.direction + + val targetDirectionId: Long? get() = this.targetDirection?.id + + override fun toJSONString() = toJSONString(this) + } +} diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderExt.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderExt.kt index c73886a4..53a02a56 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderExt.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderExt.kt @@ -54,7 +54,7 @@ private fun

P.getCachedServiceUpdatesS( tables = dbTableName projectionMap = ServiceUpdateProvider.SERVICE_UPDATE_PROJECTION_MAP }.query( - getReadDB(), ServiceUpdateProviderContract.PROJECTION_SERVICE_UPDATE, selection, null, null, null, null, null + readDB, ServiceUpdateProviderContract.PROJECTION_SERVICE_UPDATE, selection, null, null, null, null, null ).use { cursor -> buildServiceUpdates { if (cursor != null && cursor.count > 0) { 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 bb2b2075..2f29bf07 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 @@ -10,6 +10,7 @@ import org.mtransit.android.commons.TimeUtils import org.mtransit.android.commons.TimeUtilsK import org.mtransit.android.commons.data.POIStatus import org.mtransit.android.commons.data.Schedule +import org.mtransit.android.commons.data.ScheduleStatusFilter import org.mtransit.android.commons.data.arrival import org.mtransit.android.commons.data.departure import org.mtransit.android.commons.data.makeSchedule @@ -86,7 +87,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.getCached(statusFilter: StatusProviderContract.Filter): POIStatus? { - val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { + val filter = statusFilter as? ScheduleStatusFilter ?: run { MTLog.w(LOG_TAG, "getCached() > Can't find new schedule without schedule filter!") return null } @@ -103,7 +104,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { private val tripUpdateLock = mutableMapOf() private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyDataLock( - filter: Schedule.ScheduleStatusFilter, + filter: ScheduleStatusFilter, tripIds: List ): POIStatus? { val context = context ?: return null @@ -132,7 +133,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { private fun GTFSRealTimeProvider.makeCachedStatusFromAgencyData( context: Context, - filter: Schedule.ScheduleStatusFilter, + filter: ScheduleStatusFilter, staticTripIds: List, ): POIStatus? { MTLog.d(LOG_TAG, "makeCachedStatusFromAgencyData(${filter.targetUUID}, ${staticTripIds.size})") @@ -297,7 +298,7 @@ object GTFSRealTimeTripUpdatesProvider : MTLog.Loggable { @JvmStatic fun GTFSRealTimeProvider.getNew(statusFilter: StatusProviderContract.Filter): POIStatus? { - val filter = statusFilter as? Schedule.ScheduleStatusFilter ?: run { + val filter = statusFilter as? ScheduleStatusFilter ?: run { MTLog.w(LOG_TAG, "getNew() > Can't find new schedule without schedule filter!") return null } diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java b/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java index ec2c46ab..4c302097 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProvider.java @@ -17,10 +17,13 @@ import org.mtransit.android.commons.StringUtils; import org.mtransit.android.commons.TimeUtils; import org.mtransit.android.commons.data.AppStatus; +import org.mtransit.android.commons.data.AppStatusFilter; import org.mtransit.android.commons.data.AvailabilityPercent; +import org.mtransit.android.commons.data.AvailabilityPercentStatusFilter; import org.mtransit.android.commons.data.POI; import org.mtransit.android.commons.data.POIStatus; import org.mtransit.android.commons.data.Schedule; +import org.mtransit.android.commons.data.ScheduleStatusFilter; import org.mtransit.android.commons.provider.common.ContentProviderConstants; import org.mtransit.android.commons.provider.common.MTContentProvider; import org.mtransit.android.commons.provider.common.MTSQLiteOpenHelper; @@ -140,13 +143,13 @@ private static StatusProviderContract.Filter extractStatusFilter(@Nullable Strin statusFilter = null; break; case POI.ITEM_STATUS_TYPE_SCHEDULE: - statusFilter = Schedule.ScheduleStatusFilter.fromJSONString(selection); + statusFilter = ScheduleStatusFilter.fromJSONString(selection); break; case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT: - statusFilter = AvailabilityPercent.AvailabilityPercentStatusFilter.fromJSONString(selection); + statusFilter = AvailabilityPercentStatusFilter.fromJSONString(selection); break; case POI.ITEM_STATUS_TYPE_APP: - statusFilter = AppStatus.AppStatusFilter.fromJSONString(selection); + statusFilter = AppStatusFilter.fromJSONString(selection); break; default: MTLog.w(LOG_TAG, "Unexpected status filter type '%s'!", type); @@ -215,7 +218,7 @@ public static POIStatus getCachedStatusS( SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables(provider.getStatusDbTableName()); qb.setProjectionMap(STATUS_PROJECTION_MAP); - cursor = qb.query(provider.getReadDB(), PROJECTION_STATUS, selection, null, null, null, STATUS_SORT_ORDER, STATUS_LIMIT); + cursor = qb.query(provider.getReadDB(), StatusProviderContract.getPROJECTION_STATUS(), selection, null, null, null, STATUS_SORT_ORDER, STATUS_LIMIT); if (cursor != null && cursor.getCount() > 0) { if (cursor.moveToFirst()) { final int type = POIStatus.getTypeFromCursor(cursor); diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java deleted file mode 100644 index 43e23d77..00000000 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.java +++ /dev/null @@ -1,148 +0,0 @@ -package org.mtransit.android.commons.provider.status; - -import android.net.Uri; -import android.provider.BaseColumns; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; -import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.data.POI; -import org.mtransit.android.commons.data.POIStatus; -import org.mtransit.android.commons.provider.common.ProviderContract; - -public interface StatusProviderContract extends ProviderContract { - - String STATUS_PATH = "status"; - - long getStatusMaxValidityInMs(); - - long getStatusValidityInMs(boolean inFocus); - - long getMinDurationBetweenRefreshInMs(boolean inFocus); - - @Nullable - POIStatus getNewStatus(@NonNull Filter statusFilter); - - void cacheStatus(@NonNull POIStatus newStatusToCache); - - @Nullable - POIStatus getCachedStatus(@NonNull Filter statusFilter); - - boolean purgeUselessCachedStatuses(); - - boolean deleteCachedStatus(int cachedStatusId); - - @NonNull - Uri getAuthorityUri(); - - int getStatusType(); - - @NonNull - String getStatusDbTableName(); - - String[] PROJECTION_STATUS = new String[]{ - Columns.T_STATUS_K_ID, - Columns.T_STATUS_K_TYPE, - Columns.T_STATUS_K_TARGET_UUID, - Columns.T_STATUS_K_LAST_UPDATE, - Columns.T_STATUS_K_VALIDITY, - Columns.T_STATUS_K_READ_FROM_SOURCE_AT, - Columns.T_STATUS_K_EXTRAS - }; - - class Columns { - public static final String T_STATUS_K_ID = BaseColumns._ID; - public static final String T_STATUS_K_TYPE = "type"; - public static final String T_STATUS_K_TARGET_UUID = "target"; - public static final String T_STATUS_K_EXTRAS = "extras"; - public static final String T_STATUS_K_LAST_UPDATE = "last_update"; - public static final String T_STATUS_K_VALIDITY = "max_validity"; - public static final String T_STATUS_K_READ_FROM_SOURCE_AT = "read_from_source_at"; - } - - @SuppressWarnings("WeakerAccess") - abstract class Filter extends ProviderContract.Filter implements MTLog.Loggable { - - private static final String LOG_TAG = StatusProviderContract.class.getSimpleName() + ">" + Filter.class.getSimpleName(); - - @NonNull - @Override - public String getLogTag() { - return LOG_TAG; - } - - @NonNull - private String targetUUID; - @POI.ItemStatusType - private int type; - - public Filter(@POI.ItemStatusType int type, @NonNull String targetUUID) { - this.type = type; - this.targetUUID = targetUUID; - } - - @NonNull - public String getTargetUUID() { - return this.targetUUID; - } - - @POI.ItemStatusType - public int getType() { - return this.type; - } - - public static int getTypeFromJSONString(@Nullable String jsonString) { - try { - return jsonString == null ? -1 : getTypeFromJSON(new JSONObject(jsonString)); - } catch (JSONException jsone) { - MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '%s'", jsonString); - return -1; - } - } - - public static int getTypeFromJSON(@NonNull JSONObject json) throws JSONException { - return json.getInt(JSON_TYPE); - } - - @NonNull - public static String getTargetUUIDFromJSON(@NonNull JSONObject json) throws JSONException { - return json.getString(JSON_TARGET_UUID); - } - - public static void toJSON(@NonNull Filter statusFilter, @NonNull JSONObject json) throws JSONException { - ProviderContract.Filter.toJSON(statusFilter, json); - json.put(JSON_TYPE, statusFilter.getType()); - json.put(JSON_TARGET_UUID, statusFilter.getTargetUUID()); - } - - private static final String JSON_TYPE = "type"; - private static final String JSON_TARGET_UUID = "target"; - - public static void fromJSON(@NonNull Filter statusFilter, @NonNull JSONObject json) throws JSONException { - ProviderContract.Filter.fromJSON(statusFilter, json); - statusFilter.type = json.getInt(JSON_TYPE); - statusFilter.targetUUID = json.getString(JSON_TARGET_UUID); - } - - @Nullable - @SuppressWarnings("unused") - public abstract Filter fromJSONStringStatic(@Nullable String jsonString); - - @SuppressWarnings("unused") - @Nullable - public abstract String toJSONStringStatic(@NonNull Filter statusFilter); - - @NonNull - @Override - public String toString() { - return Filter.class.getSimpleName() + "{" + - "targetUUID='" + targetUUID + '\'' + - ", type=" + type + - ", " + super.toStringParts() + - '}'; - } - } -} diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt new file mode 100644 index 00000000..b70713d3 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt @@ -0,0 +1,164 @@ +package org.mtransit.android.commons.provider.status + +import android.net.Uri +import android.provider.BaseColumns +import org.json.JSONException +import org.json.JSONObject +import org.mtransit.android.commons.MTLog +import org.mtransit.android.commons.data.AppStatusFilter +import org.mtransit.android.commons.data.AvailabilityPercentStatusFilter +import org.mtransit.android.commons.data.POI +import org.mtransit.android.commons.data.POIStatus +import org.mtransit.android.commons.data.RouteDirectionStop +import org.mtransit.android.commons.data.ScheduleStatusFilter +import org.mtransit.android.commons.provider.common.ProviderContract + +interface StatusProviderContract : ProviderContract { + + companion object { + const val STATUS_PATH = "status" + + @JvmStatic + val PROJECTION_STATUS = arrayOf( + Columns.T_STATUS_K_ID, + Columns.T_STATUS_K_TYPE, + Columns.T_STATUS_K_TARGET_UUID, + Columns.T_STATUS_K_LAST_UPDATE, + Columns.T_STATUS_K_VALIDITY, + Columns.T_STATUS_K_READ_FROM_SOURCE_AT, + Columns.T_STATUS_K_EXTRAS + ) + } + + val statusMaxValidityInMs: Long + + fun getStatusValidityInMs(inFocus: Boolean): Long + + fun getMinDurationBetweenRefreshInMs(inFocus: Boolean): Long + + fun getNewStatus(statusFilter: Filter): POIStatus? + + fun cacheStatus(newStatusToCache: POIStatus) + + fun getCachedStatus(statusFilter: Filter): POIStatus? + + fun purgeUselessCachedStatuses(): Boolean + + fun deleteCachedStatus(cachedStatusId: Int): Boolean + + val authorityUri: Uri + + val statusType: Int + + val statusDbTableName: String + + object Columns { + const val T_STATUS_K_ID: String = BaseColumns._ID + const val T_STATUS_K_TYPE = "type" + const val T_STATUS_K_TARGET_UUID = "target" + const val T_STATUS_K_EXTRAS = "extras" + const val T_STATUS_K_LAST_UPDATE = "last_update" + const val T_STATUS_K_VALIDITY = "max_validity" + const val T_STATUS_K_READ_FROM_SOURCE_AT = "read_from_source_at" + } + + abstract class Filter( + val type: Int, + open val targetUUID: String, + ) : ProviderContract.Filter(), MTLog.Loggable { + + companion object { + private val LOG_TAG: String = StatusProviderContract::class.java.getSimpleName() + ">" + Filter::class.java.getSimpleName() + + private const val JSON_TYPE = "type" + private const val JSON_TARGET_UUID = "target" + + @JvmStatic + fun from( + poi: POI, + inFocus: Boolean?, + scheduleBehindInMs: Long?, + scheduleMaxDataRequests: Int?, + scheduleIncludeCancelledTimestamps: Boolean?, + getAppPkg: () -> String? + ): Filter? = when (poi.statusType) { + POI.ITEM_STATUS_TYPE_NONE -> null + POI.ITEM_STATUS_TYPE_SCHEDULE -> { + (poi as? RouteDirectionStop)?.let { + ScheduleStatusFilter( + inFocus = inFocus, + routeDirectionStop = it, + lookBehindInMs = scheduleBehindInMs, + maxDataRequests = scheduleMaxDataRequests, + includeCancelledTimestamps = scheduleIncludeCancelledTimestamps + ) + } ?: run { + MTLog.w(this, "Schedule filter w/o '$poi'!") + null + } + } + + POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT -> AvailabilityPercentStatusFilter( + targetUUID = poi.uuid, + inFocus = inFocus, + ) + + POI.ITEM_STATUS_TYPE_APP -> { + getAppPkg()?.let { pkg -> + AppStatusFilter(inFocus = inFocus, targetUUID = poi.uuid, pkg = pkg) + } ?: run { + MTLog.w(this, "App status filter w/o '$poi'!") + null + } + } + + else -> { + MTLog.w(this, "Unexpected status type '${poi.statusType}' for filter!") + null + } + } + + @JvmStatic + fun getTypeFromJSONString(jsonString: String?): Int { + try { + return if (jsonString == null) -1 else getTypeFromJSON(JSONObject(jsonString)) + } catch (jsone: JSONException) { + MTLog.w(LOG_TAG, jsone, "Error while parsing JSON string '$jsonString'") + return -1 + } + } + + @Throws(JSONException::class) + fun getTypeFromJSON(json: JSONObject) = json.getInt(JSON_TYPE) + + @JvmStatic + @Throws(JSONException::class) + fun getTargetUUIDFromJSON(json: JSONObject): String = json.getString(JSON_TARGET_UUID) + + @Throws(JSONException::class) + fun toJSON(statusFilter: Filter, json: JSONObject) { + ProviderContract.Filter.toJSON(statusFilter, json) + json.put(JSON_TYPE, statusFilter.type) + json.put(JSON_TARGET_UUID, statusFilter.targetUUID) + } + } + + override fun getLogTag() = LOG_TAG + + abstract fun copyWith(providedEncryptKeysMap: Map?): Filter + + @Suppress("unused") + abstract fun fromJSONStringStatic(jsonString: String?): Filter? + + @Suppress("unused") + abstract fun toJSONStringStatic(statusFilter: Filter): String? + + override fun toString(): String { + return Filter::class.java.getSimpleName() + "{" + + "targetUUID='" + targetUUID + '\'' + + ", type=" + type + + ", " + super.toStringParts() + + '}' + } + } +} diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt index 00ff1a01..ba51cebf 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/NextBusVehicleLocationsProvider.kt @@ -57,7 +57,7 @@ object NextBusVehicleLocationsProvider { @JvmStatic fun NextBusProvider.getCached(filter: VehicleLocationProviderContract.Filter): List? = - ((filter.poi as? RouteDirectionStop)?.getTargetUUIDs(this) + (filter.rds?.getTargetUUIDs(this) ?: filter.routeDirection?.getTargetUUIDs(this) ?: filter.route?.getTargetUUIDs(this)) ?.let { targetUUIDs -> diff --git a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt index 62611fa0..39decec8 100644 --- a/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/vehiclelocations/VehicleLocationProvider.kt @@ -32,7 +32,7 @@ abstract class VehicleLocationProvider : MTContentProvider(), @JvmStatic fun

P.queryS(uri: Uri, selection: String?): Cursor? { - return when (getURI_MATCHER().match(uri)) { + return when (URI_MATCHER.match(uri)) { ContentProviderConstants.PING -> ContentProviderConstants.EMPTY_CURSOR // empty cursor = processed ContentProviderConstants.VEHICLE_LOCATION -> getVehicleLocations(selection) else -> null // not processed @@ -117,7 +117,7 @@ abstract class VehicleLocationProvider : MTContentProvider(), @JvmStatic fun

P.getTypeS(uri: Uri): String? { - return when (getURI_MATCHER().match(uri)) { + return when (URI_MATCHER.match(uri)) { ContentProviderConstants.PING, ContentProviderConstants.VEHICLE_LOCATION -> StringUtils.EMPTY // empty string = processed else -> null // not processed @@ -198,7 +198,7 @@ abstract class VehicleLocationProvider : MTContentProvider(), fun cacheVehicleLocationsS(provider: VehicleLocationProviderContract, newVehicleLocations: List?): Int { var affectedRows = 0 try { - provider.getWriteDB().transaction { + provider.writeDB.transaction { newVehicleLocations?.forEach { vehicleLocation -> insert(provider.dbTableName, VehicleLocationDbHelper.T_VEHICLE_LOCATION_K_ID, vehicleLocation.toContentValues()) .let { rowId -> @@ -216,7 +216,7 @@ abstract class VehicleLocationProvider : MTContentProvider(), fun deleteAllCachedVehicleLocations(provider: VehicleLocationProviderContract): Boolean { var deletedRows = 0 try { - deletedRows = provider.getWriteDB().delete(provider.dbTableName, null, null) + deletedRows = provider.writeDB.delete(provider.dbTableName, null, null) } catch (e: Exception) { MTLog.w(LOG_TAG, e, "Error while deleting ALL cached vehicle locations!") } @@ -229,7 +229,7 @@ abstract class VehicleLocationProvider : MTContentProvider(), val selection = SqlUtils.getWhereEquals(VehicleLocationProviderContract.Columns.T_VEHICLE_LOCATION_K_ID, vehicleLocationId) var deletedRows = 0 try { - deletedRows = provider.getWriteDB().delete(provider.dbTableName, selection, null) + deletedRows = provider.writeDB.delete(provider.dbTableName, selection, null) } catch (e: Exception) { MTLog.w(LOG_TAG, e, "Error while deleting cached vehicle location '%s'!", vehicleLocationId) } @@ -242,7 +242,7 @@ abstract class VehicleLocationProvider : MTContentProvider(), val selection = SqlUtils.getWhereInferior(VehicleLocationProviderContract.Columns.T_VEHICLE_LOCATION_K_LAST_UPDATE, oldestLastUpdate) var deletedRows = 0 try { - deletedRows = provider.getWriteDB().delete(provider.dbTableName, selection, null) + deletedRows = provider.writeDB.delete(provider.dbTableName, selection, null) } catch (e: Exception) { MTLog.w(LOG_TAG, e, "Error while deleting cached vehicle locations!") } 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 fa08b1f7..d7b6412b 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 @@ -89,6 +89,10 @@ interface VehicleLocationProviderContract : ProviderContract { } data class Filter @Discouraged("use secondary constructor() instead") constructor( + override val cacheOnly: Boolean? = null, + override val cacheValidityInMs: Long? = null, + override val inFocus: Boolean? = null, + override val providedEncryptKeysMap: Map? = null, val authority: String, override val poi: POI? = null, // RouteDirectionStop or DefaultPOI override val route: Route? = null, @@ -96,16 +100,13 @@ interface VehicleLocationProviderContract : ProviderContract { ) : ProviderContract.Filter(), GTFSRealTimeProviderFilter, MTLog.Loggable { @SuppressLint("DiscouragedApi") - constructor(poi: POI) : - this(authority = poi.authority, poi = poi) + constructor(poi: POI) : this(authority = poi.authority, poi = poi) @SuppressLint("DiscouragedApi") - constructor(route: Route) : - this(authority = route.authority, route = route) + constructor(route: Route) : this(authority = route.authority, route = route) @SuppressLint("DiscouragedApi") - constructor(routeDirection: RouteDirection) : - this(authority = routeDirection.authority, routeDirection = routeDirection) + constructor(routeDirection: RouteDirection) : this(authority = routeDirection.authority, routeDirection = routeDirection) companion object { private val LOG_TAG: String = VehicleLocationProviderContract::class.java.simpleName + ">" + Filter::class.java.simpleName @@ -126,22 +127,20 @@ interface VehicleLocationProviderContract : ProviderContract { @SuppressLint("DiscouragedApi") fun fromJSON(json: JSONObject): Filter? { - val poi = json.optJSONObject(JSON_POI)?.let { jPoi -> - DefaultPOI.fromJSONStatic(jPoi) - } - val authority = JSONUtils.optString(json, JSON_AUTHORITY) - val route = json.optJSONObject(JSON_ROUTE)?.let { jRoute -> - authority?.let { Route.fromJSON(jRoute, it) } - } - val routeDirection = json.optJSONObject(JSON_ROUTE_DIRECTION)?.let { jRouteDirection -> - authority?.let { RouteDirection.fromJSON(jRouteDirection, it) } - } - return (poi?.let { Filter(authority = it.authority, poi = it) } - ?: route?.let { Filter(authority = route.authority, route = it) } - ?: routeDirection?.let { Filter(authority = routeDirection.authority, routeDirection = it) }) - ?.apply { - fromJSON(this, json) - } + val poi = json.optJSONObject(JSON_POI)?.let { DefaultPOI.fromJSONStatic(it) } + val authority = JSONUtils.optString(json, JSON_AUTHORITY) ?: poi?.authority ?: return null + val route = json.optJSONObject(JSON_ROUTE)?.let { Route.fromJSON(it, authority) } + val routeDirection = json.optJSONObject(JSON_ROUTE_DIRECTION)?.let { RouteDirection.fromJSON(it, authority) } + return Filter( + cacheOnly = getCacheOnlyFromJSON(json), + cacheValidityInMs = getCacheValidityInMsFromJSON(json), + inFocus = getInFocusFromJSON(json), + providedEncryptKeysMap = getProvidedEncryptKeysMapFromJSON(json), + authority = authority, + poi = poi, + route = route, + routeDirection = routeDirection + ) } fun toJSONString(vehicleLocationFilter: Filter) = @@ -168,20 +167,20 @@ interface VehicleLocationProviderContract : ProviderContract { @Suppress("unused") // used from main app override fun toJSONString() = toJSONString(this) + val rds: RouteDirectionStop? get() = poi as? RouteDirectionStop + private val _route: Route? - get() = (poi as? RouteDirectionStop)?.route + get() = rds?.route ?: route ?: routeDirection?.route private val _direction: Direction? - get() = (poi as? RouteDirectionStop)?.direction + get() = rds?.direction ?: routeDirection?.direction - val routeId: Long? - get() = _route?.id + val routeId: Long? get() = _route?.id - val directionId: Long? - get() = _direction?.id + val directionId: Long? get() = _direction?.id val targetAuthority: String? get() = poi?.authority From f631562e6bc8e6ff0fbcc8048f370e5ad788b111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 13:32:35 -0400 Subject: [PATCH 07/12] wip --- .../android/commons/data/AppStatusFilter.kt | 5 +++-- .../data/AvailabilityPercentStatusFilter.kt | 5 +++-- .../android/commons/data/ScheduleStatusFilter.kt | 5 +++-- .../commons/provider/common/ProviderContract.kt | 15 ++++++++------- .../commons/provider/news/NewsProviderContract.kt | 3 ++- .../commons/provider/poi/POIProviderContract.kt | 3 ++- .../ServiceUpdateProviderContract.kt | 3 ++- .../provider/status/StatusProviderContract.kt | 3 ++- .../VehicleLocationProviderContract.kt | 3 ++- 9 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt b/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt index 7a840339..537544c1 100644 --- a/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt +++ b/src/main/java/org/mtransit/android/commons/data/AppStatusFilter.kt @@ -4,12 +4,13 @@ import org.json.JSONException import org.json.JSONObject import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.provider.status.StatusProviderContract +import org.mtransit.commons.model.Secret data class AppStatusFilter( override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, override val targetUUID: String, val pkg: String ) : StatusProviderContract.Filter(POI.ITEM_STATUS_TYPE_APP, targetUUID) { @@ -70,7 +71,7 @@ data class AppStatusFilter( override fun getLogTag() = LOG_TAG - override fun copyWith(providedEncryptKeysMap: Map?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) + override fun copyWith(providedEncryptKeysMap: Secret>?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) override fun fromJSONStringStatic(jsonString: String?) = fromJSONString(jsonString) diff --git a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt index 2b35b880..7e7deab5 100644 --- a/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt +++ b/src/main/java/org/mtransit/android/commons/data/AvailabilityPercentStatusFilter.kt @@ -4,12 +4,13 @@ import org.json.JSONException import org.json.JSONObject import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.provider.status.StatusProviderContract +import org.mtransit.commons.model.Secret data class AvailabilityPercentStatusFilter( override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, override val targetUUID: String, ) : StatusProviderContract.Filter(POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT, targetUUID) { @@ -65,7 +66,7 @@ data class AvailabilityPercentStatusFilter( override fun getLogTag() = LOG_TAG - override fun copyWith(providedEncryptKeysMap: Map?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) + override fun copyWith(providedEncryptKeysMap: Secret>?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) override fun fromJSONStringStatic(jsonString: String?) = fromJSONString(jsonString) diff --git a/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt b/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt index a5f97d48..516a71fa 100644 --- a/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt +++ b/src/main/java/org/mtransit/android/commons/data/ScheduleStatusFilter.kt @@ -8,13 +8,14 @@ import org.mtransit.android.commons.optBoolean import org.mtransit.android.commons.optInt import org.mtransit.android.commons.optLong import org.mtransit.android.commons.provider.status.StatusProviderContract +import org.mtransit.commons.model.Secret import java.util.concurrent.TimeUnit data class ScheduleStatusFilter( override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, val routeDirectionStop: RouteDirectionStop, private val lookBehindInMs: Long? = null, private val minUsefulDurationCoveredInMs: Long? = null, @@ -125,7 +126,7 @@ data class ScheduleStatusFilter( override fun getLogTag() = LOG_TAG - override fun copyWith(providedEncryptKeysMap: Map?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) + override fun copyWith(providedEncryptKeysMap: Secret>?) = this.copy(providedEncryptKeysMap = providedEncryptKeysMap) val targetAuthority: String get() = this.routeDirectionStop.authority diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt index 476d8dd4..bb9dc77e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/common/ProviderContract.kt @@ -12,6 +12,7 @@ import org.mtransit.android.commons.MTLog.Loggable import org.mtransit.android.commons.SecureStringUtils.enc import org.mtransit.android.commons.optBoolean import org.mtransit.android.commons.optLong +import org.mtransit.commons.model.Secret import java.util.concurrent.TimeUnit interface ProviderContract : Loggable { @@ -55,8 +56,8 @@ interface ProviderContract : Loggable { fun getCacheValidityInMsFromJSON(json: JSONObject) = json.optLong(JSON_CACHE_VALIDITY_IN_MS, null) @Throws(JSONException::class) - fun getProvidedEncryptKeysMapFromJSON(json: JSONObject): Map? = - json.optJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)?.let { JSONUtils.toMapOfStrings(it) } + fun getProvidedEncryptKeysMapFromJSON(json: JSONObject): Secret>? = + json.optJSONObject(JSON_PROVIDED_ENCRYPT_KEYS_MAP)?.let { JSONUtils.toMapOfStrings(it) }?.let { Secret(it) } @JvmStatic @Throws(JSONException::class) @@ -64,13 +65,13 @@ interface ProviderContract : Loggable { cacheOnly?.let { json.put(JSON_CACHE_ONLY, it) } cacheValidityInMs?.let { json.put(JSON_CACHE_VALIDITY_IN_MS, it) } inFocus?.let { json.put(JSON_IN_FOCUS, it) } - providedEncryptKeysMap?.let { json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(it)) } + providedEncryptKeysMap?.let { json.put(JSON_PROVIDED_ENCRYPT_KEYS_MAP, JSONUtils.toJSONObject(it.data)) } } fun toProvidedKeys(keysMap: Map?) = keysMap?.mapNotNull { val enc = enc(it.value) ?: return@mapNotNull null it.key to enc - }?.toMap() + }?.toMap()?.let { Secret(it) } } abstract val cacheOnly: Boolean? @@ -81,10 +82,10 @@ interface ProviderContract : Loggable { abstract val inFocus: Boolean? val isInFocusOrDefault: Boolean get() = this.inFocus ?: IN_FOCUS_DEFAULT - abstract val providedEncryptKeysMap: Map? + abstract val providedEncryptKeysMap: Secret>? fun getProvidedEncryptKey(key: String) = - this.providedEncryptKeysMap?.get(key = key) + this.providedEncryptKeysMap?.data?.get(key = key) ?.takeIf { it.trim().isNotEmpty() } @Suppress("unused") @@ -94,7 +95,7 @@ interface ProviderContract : Loggable { cacheOnly?.let { append("cacheOnly:").append(it).append(",") } cacheValidityInMs?.let { append("cacheValidityInMs:").append(MTLog.formatDuration(it)).append(",") } inFocus?.let { append("inFocus:").append(it).append(",") } - providedEncryptKeysMap?.let { append("providedEncryptKeysMap:").append(it.size).append(",") } // no not print keys + providedEncryptKeysMap?.let { append("providedEncryptKeysMap:").append(it.data.size).append(",") } // no not print keys } } } diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt index e40f2597..83c6ebe2 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt @@ -15,6 +15,7 @@ import org.mtransit.android.commons.data.News import org.mtransit.android.commons.data.POI import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.provider.common.ProviderContract +import org.mtransit.commons.model.Secret interface NewsProviderContract : ProviderContract { @@ -120,7 +121,7 @@ interface NewsProviderContract : ProviderContract { override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, private val articlesUUIDs: List? = null, private val targetsUUIDs: List? = null, private val minCreatedAtInMs: Long? = null, diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt index f24a68f8..b26a27c6 100644 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt @@ -16,6 +16,7 @@ import org.mtransit.android.commons.SqlUtils import org.mtransit.android.commons.optDouble import org.mtransit.android.commons.provider.common.ContentProviderConstants import org.mtransit.android.commons.provider.common.ProviderContract +import org.mtransit.commons.model.Secret import java.util.Locale interface POIProviderContract : ProviderContract { @@ -92,7 +93,7 @@ interface POIProviderContract : ProviderContract { override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, val lat: Double? = null, val lng: Double? = null, val aroundDiff: Double? = null, diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt index 6e4894be..91623d43 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt @@ -18,6 +18,7 @@ import org.mtransit.android.commons.data.Targetable import org.mtransit.android.commons.optString import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter +import org.mtransit.commons.model.Secret interface ServiceUpdateProviderContract : ProviderContract { @@ -88,7 +89,7 @@ interface ServiceUpdateProviderContract : ProviderContract { override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, val authority: String?, override val poi: POI? = null, // RouteDirectionStop or DefaultPOI override val route: Route? = null, diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt index b70713d3..1d637a6e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt @@ -12,6 +12,7 @@ import org.mtransit.android.commons.data.POIStatus import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.data.ScheduleStatusFilter import org.mtransit.android.commons.provider.common.ProviderContract +import org.mtransit.commons.model.Secret interface StatusProviderContract : ProviderContract { @@ -145,7 +146,7 @@ interface StatusProviderContract : ProviderContract { override fun getLogTag() = LOG_TAG - abstract fun copyWith(providedEncryptKeysMap: Map?): Filter + abstract fun copyWith(providedEncryptKeysMap: Secret>?): Filter @Suppress("unused") abstract fun fromJSONStringStatic(jsonString: String?): Filter? 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 d7b6412b..2a1d68ee 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 @@ -17,6 +17,7 @@ import org.mtransit.android.commons.data.RouteDirectionStop import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation +import org.mtransit.commons.model.Secret interface VehicleLocationProviderContract : ProviderContract { @@ -92,7 +93,7 @@ interface VehicleLocationProviderContract : ProviderContract { override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, - override val providedEncryptKeysMap: Map? = null, + override val providedEncryptKeysMap: Secret>? = null, val authority: String, override val poi: POI? = null, // RouteDirectionStop or DefaultPOI override val route: Route? = null, From 27ed8a0c17a403067444ae3c52a58635001165a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 13:47:18 -0400 Subject: [PATCH 08/12] wip --- .../commons/provider/news/NewsProviderContract.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt index 83c6ebe2..e3cd7053 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt @@ -134,13 +134,15 @@ interface NewsProviderContract : ProviderContract { @JvmStatic fun newEmptyFilter() = Filter() - @SuppressLint("DiscouragedApi") @JvmStatic - fun newArticleUUIDFilter(articleUUID: String) = Filter(articlesUUIDs = listOf(articleUUID)) + fun newArticleUUIDFilter(articleUUID: String) = newArticlesUUIDsFilter(listOf(articleUUID)) @SuppressLint("DiscouragedApi") @JvmStatic - fun newPOIFilter(poi: POI) = Filter(targetsUUIDs = poi.toTargetsUUIDs()) + fun newArticlesUUIDsFilter(articlesUUIDs: List) = Filter(articlesUUIDs = articlesUUIDs) + + @JvmStatic + fun newPOIFilter(poi: POI) = newTargetsUUIDsFilter(poi.toTargetsUUIDs()) @JvmStatic fun POI.toTargetsUUIDs(): List = buildList { @@ -150,6 +152,10 @@ interface NewsProviderContract : ProviderContract { } } + @SuppressLint("DiscouragedApi") + @JvmStatic + fun newTargetsUUIDsFilter(targetsUUIDs: List) = Filter(targetsUUIDs = targetsUUIDs) + @JvmStatic fun fromJSONString(jsonString: String?): Filter? { try { From 5fd3804794c73d299a12921cefea076d45cf8e52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 13:52:21 -0400 Subject: [PATCH 09/12] wip --- src/main/java/org/mtransit/android/commons/JSONExt.kt | 10 +++++----- .../commons/provider/news/NewsProviderContract.kt | 2 +- .../commons/provider/poi/POIProviderContract.kt | 2 +- .../commons/provider/status/StatusProviderContract.kt | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/JSONExt.kt b/src/main/java/org/mtransit/android/commons/JSONExt.kt index f0720d18..1ca59f75 100644 --- a/src/main/java/org/mtransit/android/commons/JSONExt.kt +++ b/src/main/java/org/mtransit/android/commons/JSONExt.kt @@ -3,17 +3,17 @@ package org.mtransit.android.commons import org.json.JSONObject fun JSONObject.optLong(name: String, fallback: Long? = null) = - takeIf { it.has(name) }?.getLong(name) ?: fallback + takeIf { it.has(name) && !it.isNull(name) }?.optLong(name) ?: fallback fun JSONObject.optInt(name: String, fallback: Int? = null) = - takeIf { it.has(name) }?.optInt(name) ?: fallback + takeIf { it.has(name) && !it.isNull(name) }?.optInt(name) ?: fallback fun JSONObject.optDouble(name: String, fallback: Double? = null) = - takeIf { it.has(name) }?.optDouble(name) ?: fallback + takeIf { it.has(name) && !it.isNull(name) }?.optDouble(name) ?: fallback fun JSONObject.optBoolean(name: String, fallback: Boolean? = null) = - takeIf { it.has(name) }?.optBoolean(name) ?: fallback + takeIf { it.has(name) && !it.isNull(name) }?.optBoolean(name) ?: fallback fun JSONObject.optString(name: String, fallback: String? = null) = - takeIf { it.has(name) }?.optString(name) ?: fallback + takeIf { it.has(name) && !it.isNull(name) }?.optString(name) ?: fallback diff --git a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt index e3cd7053..9e17df57 100644 --- a/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/news/NewsProviderContract.kt @@ -117,7 +117,7 @@ interface NewsProviderContract : ProviderContract { } } - data class Filter @Discouraged("use secondary constructor() instead") constructor( + data class Filter @Discouraged("use static methods instead") constructor( override val cacheOnly: Boolean? = null, override val cacheValidityInMs: Long? = null, override val inFocus: Boolean? = null, diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt index b26a27c6..d111d221 100644 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt @@ -162,7 +162,7 @@ interface POIProviderContract : ProviderContract { @JvmStatic fun getSearchSelection(searchKeywords: List?, searchableLikeColumns: Array?, searchableEqualColumns: Array?) = buildString { if (searchKeywords.isNullOrEmpty() || searchKeywords[0].isEmpty()) { - throw UnsupportedOperationException("SQL search selection needs at least 1 keyword ($searchKeywords.size)!") + throw UnsupportedOperationException("SQL search selection needs at least 1 keyword (${searchKeywords?.size})!") } if (searchableLikeColumns?.isNotEmpty() != true && searchableEqualColumns?.isNotEmpty() != true) { throw UnsupportedOperationException("SQL search selection needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") diff --git a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt index 1d637a6e..152f0fa1 100644 --- a/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/status/StatusProviderContract.kt @@ -94,7 +94,7 @@ interface StatusProviderContract : ProviderContract { includeCancelledTimestamps = scheduleIncludeCancelledTimestamps ) } ?: run { - MTLog.w(this, "Schedule filter w/o '$poi'!") + MTLog.w(LOG_TAG, "Schedule filter w/o '$poi'!") null } } @@ -108,13 +108,13 @@ interface StatusProviderContract : ProviderContract { getAppPkg()?.let { pkg -> AppStatusFilter(inFocus = inFocus, targetUUID = poi.uuid, pkg = pkg) } ?: run { - MTLog.w(this, "App status filter w/o '$poi'!") + MTLog.w(LOG_TAG, "App status filter w/o '$poi'!") null } } else -> { - MTLog.w(this, "Unexpected status type '${poi.statusType}' for filter!") + MTLog.w(LOG_TAG, "Unexpected status type '${poi.statusType}' for filter!") null } } From 29ec930fc86df4ae819aaa6d6355a8c6d6339435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 14:08:45 -0400 Subject: [PATCH 10/12] wip --- .../common/ContentProviderConstants.java | 3 +++ .../provider/poi/POIProviderContract.kt | 27 +++++++------------ .../ServiceUpdateProviderContract.kt | 2 +- .../VehicleLocationProviderContract.kt | 3 ++- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/common/ContentProviderConstants.java b/src/main/java/org/mtransit/android/commons/provider/common/ContentProviderConstants.java index 9790167f..cd7a5b84 100644 --- a/src/main/java/org/mtransit/android/commons/provider/common/ContentProviderConstants.java +++ b/src/main/java/org/mtransit/android/commons/provider/common/ContentProviderConstants.java @@ -3,11 +3,14 @@ import android.database.Cursor; import android.database.MatrixCursor; +import kotlin.text.Regex; + public final class ContentProviderConstants { public static final Cursor EMPTY_CURSOR = new MatrixCursor(new String[]{}); public static final String SEARCH_SPLIT_ON = "[\\s\\W]"; + public static final Regex SEARCH_SPLIT_ON_REGEX = new Regex(SEARCH_SPLIT_ON); // shared URI Matcher constants (> 100) here public static final int PING = 100; diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt index d111d221..91c30c0b 100644 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt @@ -167,12 +167,8 @@ interface POIProviderContract : ProviderContract { if (searchableLikeColumns?.isNotEmpty() != true && searchableEqualColumns?.isNotEmpty() != true) { throw UnsupportedOperationException("SQL search selection needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") } - for (searchKeyword in searchKeywords) { - if (searchKeyword.isEmpty()) continue - val keywords = - searchKeyword.lowercase().split(ContentProviderConstants.SEARCH_SPLIT_ON.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - for (keyword in keywords) { - if (keyword.isEmpty()) continue + searchKeywords.filter { it.isNotEmpty() }.forEach { searchKeyword -> + searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON_REGEX).filter { it.isNotEmpty() }.forEach { keyword-> if (isNotEmpty()) append(SqlUtils.AND) append(SqlUtils.P1) var c = 0 @@ -216,13 +212,10 @@ interface POIProviderContract : ProviderContract { throw UnsupportedOperationException("SQL search selection score needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") } var c = 0 - for (searchKeyword in searchKeywords) { - if (searchKeyword.isEmpty()) continue - val keywords = searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON.toRegex()) - for (keyword in keywords) { - if (searchKeyword.isEmpty()) continue - c = getSearchSelectionScoreLikeColumns(searchableLikeColumns, keyword, c) - c = getSearchSelectionScoreEqualColumns(searchableEqualColumns, keyword, c) + searchKeywords.filter { it.isNotEmpty() }.forEach { searchKeyword -> + searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON_REGEX).filter { it.isNotEmpty() }.forEach { keyword-> + c = getSearchSelectionLikeColumns(searchableLikeColumns, keyword, c) + c = getSearchSelectionEqualColumns(searchableEqualColumns, keyword, c) } } } @@ -326,16 +319,14 @@ interface POIProviderContract : ProviderContract { } } }, - extras = json.getJSONArray(JSON_EXTRAS).let { jExtras -> + extras = json.optJSONArray(JSON_EXTRAS)?.let { jExtras -> val extras = SimpleArrayMap() for (i in 0.. buildList { diff --git a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt index 91623d43..580f69c0 100644 --- a/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/serviceupdate/ServiceUpdateProviderContract.kt @@ -121,7 +121,7 @@ interface ServiceUpdateProviderContract : ProviderContract { fun fromJSON(json: JSONObject): Filter? { try { val poi = json.optJSONObject(JSON_POI)?.let { DefaultPOI.fromJSONStatic(it) } - val authority = json.optString(JSON_AUTHORITY, fallback = null) + val authority = json.optString(JSON_AUTHORITY, fallback = null)?: poi?.authority val route = authority?.let { authority -> json.optJSONObject(JSON_ROUTE)?.let { Route.fromJSON(it, authority) } } 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 2a1d68ee..ea471e98 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 @@ -14,6 +14,7 @@ import org.mtransit.android.commons.data.POI import org.mtransit.android.commons.data.Route import org.mtransit.android.commons.data.RouteDirection import org.mtransit.android.commons.data.RouteDirectionStop +import org.mtransit.android.commons.optString import org.mtransit.android.commons.provider.common.ProviderContract import org.mtransit.android.commons.provider.gtfs.GTFSRealTimeProviderFilter import org.mtransit.android.commons.provider.vehiclelocations.model.VehicleLocation @@ -129,7 +130,7 @@ interface VehicleLocationProviderContract : ProviderContract { @SuppressLint("DiscouragedApi") fun fromJSON(json: JSONObject): Filter? { val poi = json.optJSONObject(JSON_POI)?.let { DefaultPOI.fromJSONStatic(it) } - val authority = JSONUtils.optString(json, JSON_AUTHORITY) ?: poi?.authority ?: return null + val authority = json.optString(JSON_AUTHORITY, fallback = null) ?: poi?.authority ?: return null val route = json.optJSONObject(JSON_ROUTE)?.let { Route.fromJSON(it, authority) } val routeDirection = json.optJSONObject(JSON_ROUTE_DIRECTION)?.let { RouteDirection.fromJSON(it, authority) } return Filter( From 5d00aefdb9a9ffd6ce91b6eb74f4f1a280cefc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 14:33:54 -0400 Subject: [PATCH 11/12] fix --- .../provider/poi/POIProviderContract.kt | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt index 91c30c0b..b4b2dfa8 100644 --- a/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt +++ b/src/main/java/org/mtransit/android/commons/provider/poi/POIProviderContract.kt @@ -114,8 +114,7 @@ interface POIProviderContract : ProviderContract { companion object { private val LOG_TAG = POIProviderContract::class.java.getSimpleName() + ">" + Filter::class.java.getSimpleName() - @SuppressLint("DiscouragedApi") - fun getNewEmptyFilter() = Filter() + fun getNewEmptyFilter() = getNewSqlSelectionFilter("") // need empty SQL selection to be valid @SuppressLint("DiscouragedApi") fun getNewSqlSelectionFilter(sqlSelection: String) = Filter(sqlSelection = sqlSelection) @@ -140,8 +139,8 @@ interface POIProviderContract : ProviderContract { minLat: Double, maxLat: Double, minLng: Double, maxLng: Double, optLoadedMinLat: Double?, optLoadedMaxLat: Double?, optLoadedMinLng: Double?, optLoadedMaxLng: Double? ) = Filter( - minLat = minLat, maxLat = maxLat, minLng = minLng, maxLng = maxLng, - optLoadedMinLat = optLoadedMinLat, optLoadedMaxLat = optLoadedMaxLat, optLoadedMinLng = optLoadedMinLng, optLoadedMaxLng = optLoadedMaxLng + minLat = minLat, maxLat = maxLat, minLng = minLng, maxLng = maxLng, + optLoadedMinLat = optLoadedMinLat, optLoadedMaxLat = optLoadedMaxLat, optLoadedMinLng = optLoadedMinLng, optLoadedMaxLng = optLoadedMaxLng ) fun isUUIDFilter(poiFilter: Filter?) = poiFilter?.uuids?.isNotEmpty() == true @@ -167,17 +166,20 @@ interface POIProviderContract : ProviderContract { if (searchableLikeColumns?.isNotEmpty() != true && searchableEqualColumns?.isNotEmpty() != true) { throw UnsupportedOperationException("SQL search selection needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") } - searchKeywords.filter { it.isNotEmpty() }.forEach { searchKeyword -> - searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON_REGEX).filter { it.isNotEmpty() }.forEach { keyword-> - if (isNotEmpty()) append(SqlUtils.AND) - append(SqlUtils.P1) - var c = 0 - c = getSearchSelectionLikeColumns(searchableLikeColumns, keyword, c) - @Suppress("AssignedValueIsNeverRead") - c = getSearchSelectionEqualColumns(searchableEqualColumns, keyword, c) - append(SqlUtils.P2) + searchKeywords + .filter { it.isNotEmpty() } + .forEach { searchKeyword -> + searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON_REGEX).filter { it.isNotEmpty() } + .forEach { keyword -> + if (isNotEmpty()) append(SqlUtils.AND) + append(SqlUtils.P1) + var c = 0 + c = getSearchSelectionLikeColumns(searchableLikeColumns, keyword, c) + @Suppress("AssignedValueIsNeverRead") + c = getSearchSelectionEqualColumns(searchableEqualColumns, keyword, c) + append(SqlUtils.P2) + } } - } } private fun StringBuilder.getSearchSelectionEqualColumns(searchableEqualColumns: Array?, keyword: String, c: Int): Int { @@ -212,12 +214,16 @@ interface POIProviderContract : ProviderContract { throw UnsupportedOperationException("SQL search selection score needs at least 1 searchable columns (${searchableLikeColumns?.size}|${searchableEqualColumns?.size})!") } var c = 0 - searchKeywords.filter { it.isNotEmpty() }.forEach { searchKeyword -> - searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON_REGEX).filter { it.isNotEmpty() }.forEach { keyword-> - c = getSearchSelectionLikeColumns(searchableLikeColumns, keyword, c) - c = getSearchSelectionEqualColumns(searchableEqualColumns, keyword, c) + searchKeywords + .filter { it.isNotEmpty() } + .forEach { searchKeyword -> + searchKeyword.lowercase(Locale.ENGLISH).split(ContentProviderConstants.SEARCH_SPLIT_ON_REGEX) + .filter { it.isNotEmpty() } + .forEach { keyword -> + c = getSearchSelectionLikeColumns(searchableLikeColumns, keyword, c) + c = getSearchSelectionEqualColumns(searchableEqualColumns, keyword, c) + } } - } } private const val PLUS = " + " From d9cf300f76314cefc10a8487d37fcf84689f9909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20M=C3=A9a?= Date: Fri, 26 Jun 2026 14:45:15 -0400 Subject: [PATCH 12/12] unused import --- .../provider/vehiclelocations/VehicleLocationProviderContract.kt | 1 - 1 file changed, 1 deletion(-) 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 ea471e98..03602f22 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 @@ -6,7 +6,6 @@ import android.provider.BaseColumns import androidx.annotation.Discouraged import org.json.JSONException import org.json.JSONObject -import org.mtransit.android.commons.JSONUtils import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.data.DefaultPOI import org.mtransit.android.commons.data.Direction