Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<WorkInfo> liveData = workManager.getWorkInfoByIdLiveData(request.getId());
Observer<WorkInfo> observer = new Observer<WorkInfo>() {
@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);
Comment on lines +71 to +73

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Expose LoadHtmlWorker publicly so WorkManager can instantiate it

The new worker is declared as static class LoadHtmlWorker with a package‑private constructor. WorkManager creates workers by reflection and requires both the class and its (Context, WorkerParameters) constructor to be public. With the current visibility it will throw an IllegalAccessException/NoSuchMethodException when the work runs, the request will immediately fail, and the callback observes empty output. Making the worker public static and its constructor public allows WorkManager to execute the job.

Useful? React with 👍 / 👎.

}

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";
Expand All @@ -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);
Expand All @@ -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);
}
}

public static void shutdown(Context context) {
WorkManager.getInstance(context).cancelUniqueWork(WORK_NAME);
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
}
}

4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down Expand Up @@ -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" }
Loading