From 7a53543d85d2f3beb7154c119907122068c3df11 Mon Sep 17 00:00:00 2001 From: Mihai-Cristian Condrea Date: Sun, 14 Sep 2025 12:01:52 +0300 Subject: [PATCH] Use WorkManager for license loading --- app/build.gradle | 2 + .../java/ads/managers/AppOpenAd.java | 7 + .../java/utils/OpenSourceLicensesUtils.java | 123 +++++++++++------- gradle/libs.versions.toml | 4 + 4 files changed, 91 insertions(+), 45 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index be31b511..3442b48c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -100,6 +100,8 @@ dependencies { annotationProcessor libs.glide.compiler implementation libs.retrofit2 implementation libs.retrofit2.converter.gson + implementation libs.okhttp + implementation libs.androidx.work.runtime // Testing diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/AppOpenAd.java b/app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/AppOpenAd.java index 3bd4effe..c82a91ce 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/AppOpenAd.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/ads/managers/AppOpenAd.java @@ -19,6 +19,7 @@ import com.google.android.gms.ads.LoadAdError; import com.d4rk.androidtutorials.java.ads.AdUtils; import com.google.android.gms.ads.appopen.AppOpenAd.AppOpenAdLoadCallback; +import com.d4rk.androidtutorials.java.utils.OpenSourceLicensesUtils; import java.util.Date; @@ -38,6 +39,12 @@ public void onCreate() { appOpenAdManager = new AppOpenAdManager(this); } + @Override + public void onTerminate() { + super.onTerminate(); + OpenSourceLicensesUtils.shutdown(this); + } + @OnLifecycleEvent(Event.ON_START) protected void onMoveToForeground() { appOpenAdManager.showAdIfAvailable(currentActivity); diff --git a/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java b/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java index 3dc9d3c1..54af6c96 100644 --- a/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java +++ b/app/src/main/java/com/d4rk/androidtutorials/java/utils/OpenSourceLicensesUtils.java @@ -5,29 +5,78 @@ import android.os.Looper; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.Observer; +import androidx.work.Data; +import androidx.work.ExistingWorkPolicy; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkInfo; +import androidx.work.WorkManager; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + import com.d4rk.androidtutorials.java.R; import org.commonmark.node.Node; import org.commonmark.parser.Parser; import org.commonmark.renderer.html.HtmlRenderer; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import okhttp3.ConnectionPool; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** + * Utility class for loading and parsing open source license data. + */ public class OpenSourceLicensesUtils { private static final String TAG = "OpenSourceLicensesUtils"; - private static final ExecutorService executor = Executors.newSingleThreadExecutor(); + private static final String WORK_NAME = "license_loader"; private static final Handler mainHandler = new Handler(Looper.getMainLooper()); + private static final OkHttpClient client = new OkHttpClient.Builder() + .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES)) + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(10, TimeUnit.SECONDS) + .build(); + + public static void loadHtmlData(Context context, HtmlDataCallback callback) { + Context appContext = context.getApplicationContext(); + WorkManager workManager = WorkManager.getInstance(appContext); + OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(LoadHtmlWorker.class) + .addTag(WORK_NAME) + .build(); + workManager.enqueueUniqueWork(WORK_NAME, ExistingWorkPolicy.REPLACE, request); + LiveData liveData = workManager.getWorkInfoByIdLiveData(request.getId()); + Observer observer = new Observer() { + @Override + public void onChanged(WorkInfo info) { + if (info != null && info.getState().isFinished()) { + Data output = info.getOutputData(); + final String changelogHtml = output.getString("changelogHtml"); + final String eulaHtml = output.getString("eulaHtml"); + mainHandler.post(() -> callback.onHtmlDataLoaded(changelogHtml, eulaHtml)); + liveData.removeObserver(this); + } + } + }; + liveData.observeForever(observer); + } + + static class LoadHtmlWorker extends Worker { + LoadHtmlWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); + } - public static void loadHtmlData(final Context context, final HtmlDataCallback callback) { - executor.execute(() -> { + @NonNull + @Override + public Result doWork() { + Context context = getApplicationContext(); String packageName = context.getPackageName(); String currentVersion = getAppVersion(context); String changelogUrl = "https://raw.githubusercontent.com/MihaiCristianCondrea/" + packageName + "/refs/heads/main/CHANGELOG.md"; @@ -40,58 +89,35 @@ public static void loadHtmlData(final Context context, final HtmlDataCallback ca String eulaMarkdown = loadMarkdown(context, eulaUrl, R.string.error_loading_eula); String eulaHtml = markdownToHtml(eulaMarkdown); - mainHandler.post(() -> callback.onHtmlDataLoaded(changelogHtml, eulaHtml)); - }); + Data output = new Data.Builder() + .putString("changelogHtml", changelogHtml) + .putString("eulaHtml", eulaHtml) + .build(); + return Result.success(output); + } } private static String loadMarkdown(Context context, String urlString, int errorStringId) { - HttpURLConnection connection = null; - BufferedReader reader = null; - try { - URL url = new URL(urlString); - connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setConnectTimeout(10000); - connection.setReadTimeout(10000); - - int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); - StringBuilder content = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - content.append(line).append("\n"); - } - return content.toString(); + Request request = new Request.Builder().url(urlString).build(); + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + return response.body().string(); } else { - Log.e(TAG, "Failed to load URL: " + urlString + " with response code: " + responseCode); + Log.e(TAG, "Failed to load URL: " + urlString + " with response code: " + (response != null ? response.code() : -1)); return context.getString(errorStringId); } } catch (Exception e) { Log.e(TAG, "Error loading markdown from URL: " + urlString, e); return context.getString(errorStringId); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (Exception e) { - Log.e(TAG, "Error closing reader", e); - } - } - if (connection != null) { - connection.disconnect(); - } } } private static String extractLatestVersionChangelog(String markdown, String currentVersion) { - // Define the regex pattern to match the latest version section String regexPattern = "(?m)^#\\s+Version\\s+" + Pattern.quote(currentVersion) + ":\\s*(.*?)^(#\\s+Version\\s+|$)"; Pattern pattern = Pattern.compile(regexPattern, Pattern.DOTALL | Pattern.MULTILINE); Matcher matcher = pattern.matcher(markdown); if (matcher.find()) { - // Group 1 contains the changelog for the current version return Objects.requireNonNull(matcher.group(1)).trim(); } else { Log.e(TAG, "No changelog available for version " + currentVersion); @@ -113,11 +139,18 @@ private static String getAppVersion(Context context) { .versionName; } catch (Exception e) { Log.e(TAG, "Error getting app version", e); - return "1.0.0"; // Fallback version + return "1.0.0"; } } public interface HtmlDataCallback { void onHtmlDataLoaded(String changelogHtml, String eulaHtml); } -} \ No newline at end of file + + public static void shutdown(Context context) { + WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME); + client.dispatcher().executorService().shutdown(); + client.connectionPool().evictAll(); + } +} + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f62cc9df..9c0a1e55 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,6 +30,8 @@ hilt = "2.57.1" room = "2.8.0" glide = "5.0.4" retrofit = "3.0.0" +okhttp = "4.12.0" +work = "2.9.0" [libraries] aboutlibraries = { module = "com.mikepenz:aboutlibraries", version.ref = "aboutlibraries" } @@ -73,3 +75,5 @@ glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" } retrofit2 = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit2-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "work" }