diff --git a/build.gradle b/build.gradle index c65c48de..d1e3c668 100644 --- a/build.gradle +++ b/build.gradle @@ -92,6 +92,7 @@ dependencies { exclude group: "org.slf4j", module: "slf4j-api" // from "org.apache.oltu.oauth2" (not used) } implementation(libs.guava) // used by AndroidX & Twitter SDK + testImplementation(libs.bundles.test.unit) } diff --git a/src/main/java/org/mtransit/android/commons/KeysIds.kt b/src/main/java/org/mtransit/android/commons/KeysIds.kt new file mode 100644 index 00000000..b2020e43 --- /dev/null +++ b/src/main/java/org/mtransit/android/commons/KeysIds.kt @@ -0,0 +1,15 @@ +package org.mtransit.android.commons + +object KeysIds : MTLog.Loggable { + + private val LOG_TAG: String = KeysIds::class.java.simpleName + + override fun getLogTag() = LOG_TAG + + const val GOOGLE_PLACES_API_KEY = "google_places_api_key" // poi + const val TWITTER_BEARER_TOKEN = "twitter_bearer_token" // news + const val YOUTUBE_API_KEY = "youtube_api_key" // news + + // custom + const val CA_WINNIPEG_TRANSIT_API = "ca_winnipeg_transit_api_key" // news & status +} \ No newline at end of file diff --git a/src/main/java/org/mtransit/android/commons/provider/JCDecauxBikeStationProvider.java b/src/main/java/org/mtransit/android/commons/provider/JCDecauxBikeStationProvider.java index 146a0585..1b251d23 100644 --- a/src/main/java/org/mtransit/android/commons/provider/JCDecauxBikeStationProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/JCDecauxBikeStationProvider.java @@ -31,6 +31,7 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLHandshakeException; +@Deprecated // use GBFS @SuppressLint("Registered") public class JCDecauxBikeStationProvider extends BikeStationProvider { diff --git a/src/main/java/org/mtransit/android/commons/provider/NewsProviderContract.java b/src/main/java/org/mtransit/android/commons/provider/NewsProviderContract.java index 9cfe3225..c1a1b618 100644 --- a/src/main/java/org/mtransit/android/commons/provider/NewsProviderContract.java +++ b/src/main/java/org/mtransit/android/commons/provider/NewsProviderContract.java @@ -1,6 +1,5 @@ package org.mtransit.android.commons.provider; -import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; @@ -14,7 +13,6 @@ import org.mtransit.android.commons.ArrayUtils; import org.mtransit.android.commons.MTLog; -import org.mtransit.android.commons.R; import org.mtransit.android.commons.SecureStringUtils; import org.mtransit.android.commons.SqlUtils; import org.mtransit.android.commons.data.News; @@ -354,12 +352,27 @@ 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 NewsProviderContract.Filter appendProvidedEncryptedKeys(Context context) { - Map providedEncryptKeysMap = new HashMap<>(); - providedEncryptKeysMap.put(TwitterNewsProvider.TWITTER_BEARER_TOKEN, SecureStringUtils.enc(context.getString(R.string.twitter_bearer_token))); - setProvidedEncryptKeysMap(providedEncryptKeysMap); - return this; + public NewsProviderContract.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 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 11e9a53d..cb040ec8 100644 --- a/src/main/java/org/mtransit/android/commons/provider/TwitterNewsProvider.kt +++ b/src/main/java/org/mtransit/android/commons/provider/TwitterNewsProvider.kt @@ -19,6 +19,7 @@ import com.twitter.clientlib.model.Tweet import com.twitter.clientlib.model.Variant import com.twitter.clientlib.model.Video import org.mtransit.android.commons.HtmlUtils +import org.mtransit.android.commons.KeysIds import org.mtransit.android.commons.LocaleUtils import org.mtransit.android.commons.MTLog import org.mtransit.android.commons.NetworkUtils @@ -42,8 +43,6 @@ class TwitterNewsProvider : NewsProvider() { companion object { private val LOG_TAG: String = TwitterNewsProvider::class.java.simpleName - const val TWITTER_BEARER_TOKEN = "twitter_bearer_token" - private val NEWS_MAX_VALIDITY_IN_MS = MAX_CACHE_VALIDITY_MS private val NEWS_VALIDITY_IN_MS = TimeUnit.DAYS.toMillis(1L) * 10L private val NEWS_VALIDITY_IN_FOCUS_IN_MS = TimeUnit.HOURS.toMillis(1L) * 10L @@ -277,10 +276,8 @@ class TwitterNewsProvider : NewsProvider() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return getCachedNews(newsFilter) } + this.providedBearerToken = SecureStringUtils.dec(newsFilter.getProvidedEncryptKey(KeysIds.TWITTER_BEARER_TOKEN)) updateAgencyNewsDataIfRequired(requireContextCompat(), newsFilter.isInFocusOrDefault) - this.providedBearerToken = newsFilter.providedEncryptKeysMap?.get(TWITTER_BEARER_TOKEN)?.takeIf { it.isNotBlank() }?.let { - SecureStringUtils.dec(it) - } return getCachedNews(newsFilter) } @@ -349,7 +346,7 @@ class TwitterNewsProvider : NewsProvider() { private fun getTwitterApi() = _twitterApi ?: createTwitterApi().also { _twitterApi = it } - private fun createTwitterApi() = (providedBearerToken ?: _bearerToken).takeIf { it.isNotBlank() }?.let { bearerToken -> + private fun createTwitterApi() = (this.providedBearerToken ?: this._bearerToken).takeIf { it.isNotBlank() }?.let { bearerToken -> val apiClient = ApiClient(NetworkUtils.makeNewOkHttpClientWithInterceptor(context)) apiClient.setTwitterCredentials(TwitterCredentialsBearer(bearerToken)) TwitterApi(apiClient) 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 7c1e4feb..ed865b6e 100644 --- a/src/main/java/org/mtransit/android/commons/provider/WinnipegTransitProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/WinnipegTransitProvider.java @@ -20,11 +20,13 @@ import org.mtransit.android.commons.ArrayUtils; import org.mtransit.android.commons.FileUtils; import org.mtransit.android.commons.HtmlUtils; +import org.mtransit.android.commons.KeysIds; import org.mtransit.android.commons.LocaleUtils; import org.mtransit.android.commons.MTLog; import org.mtransit.android.commons.NetworkUtils; import org.mtransit.android.commons.PreferenceUtils; import org.mtransit.android.commons.R; +import org.mtransit.android.commons.SecureStringUtils; import org.mtransit.android.commons.SqlUtils; import org.mtransit.android.commons.StringUtils; import org.mtransit.android.commons.ThreadSafeDateFormatter; @@ -133,6 +135,9 @@ private static String getAPI_KEY(@NonNull Context context) { return apiKey; } + @Nullable + private String providedApiKey = null; + @Nullable private static String newsAuthorName = null; @@ -624,6 +629,7 @@ public Collection getNewsLanguages() { @Nullable @Override public ArrayList getNewNews(@NonNull NewsProviderContract.Filter newsFilter) { + this.providedApiKey = SecureStringUtils.dec(newsFilter.getProvidedEncryptKey(KeysIds.CA_WINNIPEG_TRANSIT_API)); updateAgencyNewsDataIfRequired(requireContextCompat(), newsFilter.isInFocusOrDefault()); return getCachedNews(newsFilter); } @@ -677,9 +683,9 @@ private void updateAllAgencyNewsDataFromWWW(@NonNull Context context, boolean de private static final String NEWS_URL_PART_1_BEFORE_API_KEY = "https://api.winnipegtransit.com/v2/service-advisories.json?api-key="; @NonNull - private static String getNewsUrlString(@NonNull Context context) { - return NEWS_URL_PART_1_BEFORE_API_KEY + // - getAPI_KEY(context); + private String getNewsUrlString(@NonNull Context context) { + return NEWS_URL_PART_1_BEFORE_API_KEY + + (this.providedApiKey != null ? this.providedApiKey : getAPI_KEY(context)); } @Nullable @@ -752,7 +758,7 @@ private ArrayList parseAgencyNewsJSON(@NonNull Context context, URL fromUR try { ArrayList news = new ArrayList<>(); JSONObject json = jsonString == null ? null : new JSONObject(jsonString); - if (context != null && json != null && json.has(JSON_SERVICE_ADVISORIES)) { + if (json != null && json.has(JSON_SERVICE_ADVISORIES)) { JSONArray jServiceAdvisories = json.getJSONArray(JSON_SERVICE_ADVISORIES); long noteworthyInMs = Long.parseLong(context.getResources().getString(R.string.news_provider_noteworthy_long_term)); int defaultPriority = context.getResources().getInteger(R.integer.news_provider_severity_info_agency); diff --git a/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.java b/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.java index 42f66672..261fa986 100644 --- a/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.java +++ b/src/main/java/org/mtransit/android/commons/provider/YouTubeNewsProvider.java @@ -16,11 +16,13 @@ import org.mtransit.android.commons.ArrayUtils; import org.mtransit.android.commons.FileUtils; import org.mtransit.android.commons.HtmlUtils; +import org.mtransit.android.commons.KeysIds; import org.mtransit.android.commons.LocaleUtils; import org.mtransit.android.commons.MTLog; import org.mtransit.android.commons.NetworkUtils; import org.mtransit.android.commons.PreferenceUtils; import org.mtransit.android.commons.R; +import org.mtransit.android.commons.SecureStringUtils; import org.mtransit.android.commons.SqlUtils; import org.mtransit.android.commons.StringUtils; import org.mtransit.android.commons.ThreadSafeDateFormatter; @@ -124,6 +126,9 @@ private static String getAPI_KEY(@NonNull Context context) { return apiKey; } + @Nullable + private String providedApiKey = null; + @Nullable private static java.util.List channelsUploadsPlaylistId = null; @@ -411,6 +416,7 @@ public Collection getNewsLanguages() { @Nullable @Override public ArrayList getNewNews(@NonNull NewsProviderContract.Filter newsFilter) { + this.providedApiKey = SecureStringUtils.dec(newsFilter.getProvidedEncryptKey(KeysIds.YOUTUBE_API_KEY)); updateAgencyNewsDataIfRequired(requireContextCompat(), newsFilter.isInFocusOrDefault()); return getCachedNews(newsFilter); } @@ -518,8 +524,13 @@ private String getChannelUploadsPlaylistUrl(@NonNull String apiKey, @NonNull Str @Nullable private ArrayList loadAgencyNewsDataFromWWW(@NonNull Context context, @NonNull String channelUploadsPlaylistId, int i) { try { + final String apiKey = this.providedApiKey != null ? this.providedApiKey : getAPI_KEY(context); + if (apiKey.isEmpty()) { + MTLog.w(this, "Loading from '%s' > SKIP (no API key)", getChannelUploadsPlaylistUrl("API_KEY", channelUploadsPlaylistId)); + return null; + } MTLog.i(this, "Loading from '%s'...", getChannelUploadsPlaylistUrl("API_KEY", channelUploadsPlaylistId)); - String urlString = getChannelUploadsPlaylistUrl(getAPI_KEY(context), channelUploadsPlaylistId); + String urlString = getChannelUploadsPlaylistUrl(apiKey, channelUploadsPlaylistId); URL url = new URL(urlString); URLConnection urlConnection = url.openConnection(); NetworkUtils.setupUrlConnection(urlConnection);