Modernize the Java code

This commit is contained in:
Alula
2025-09-05 23:33:22 +02:00
parent 7615b57e66
commit 0caa8f4376
6 changed files with 141 additions and 142 deletions
+2 -2
View File
@@ -73,8 +73,8 @@ android {
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
@@ -1,16 +1,15 @@
package io.github.doukutsu_rs;
import android.app.Activity;
import android.view.Window;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
public class ActivityUtils {
public static void hideSystemBars(Activity activity) {
Window window = activity.getWindow();
WindowInsetsControllerCompat windowInsetsController =
WindowCompat.getInsetsController(window, window.getDecorView());
var window = activity.getWindow();
var windowInsetsController =
WindowCompat.getInsetsController(window, window.getDecorView());
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars());
}
}
@@ -1,10 +1,11 @@
package io.github.doukutsu_rs;
import static android.os.Build.VERSION.SDK_INT;
import android.content.ContentResolver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.net.Uri;
import android.os.Build;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -24,8 +25,8 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.util.LinkedList;
import static android.os.Build.VERSION.SDK_INT;
import java.util.Locale;
import java.util.Objects;
public class DoukutsuDocumentsProvider extends DocumentsProvider {
private final static String[] DEFAULT_ROOT_PROJECTION =
@@ -51,14 +52,14 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
File file = getContext().getFilesDir();
String id = file.getAbsolutePath();
var file = getContext().getFilesDir();
var id = file.getAbsolutePath();
Log.d(DoukutsuDocumentsProvider.class.getName(), "files dir location: " + id);
MatrixCursor result = new MatrixCursor(projection != null ?
var result = new MatrixCursor(projection != null ?
projection : DEFAULT_ROOT_PROJECTION);
RowBuilder row = result.newRow();
var row = result.newRow();
row.add(Root.COLUMN_DOCUMENT_ID, id);
row.add(Root.COLUMN_ROOT_ID, id);
@@ -74,7 +75,7 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
@Override
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
var result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
Log.d("dupa", "queryDocument: " + documentId);
pushFile(result, new File(documentId));
@@ -84,9 +85,9 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
@Override
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
MatrixCursor result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
var result = new MatrixCursor(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
File root = new File(parentDocumentId);
var root = new File(parentDocumentId);
Log.d("dupa", "doc id:" + parentDocumentId);
if (!root.exists()) {
@@ -99,35 +100,35 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
return null;
}
File[] files = root.listFiles();
var files = root.listFiles();
if (files != null) {
for (File file : files) {
for (var file : files) {
pushFile(result, file);
}
}
result.setNotificationUri(getContext().getContentResolver(), DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, parentDocumentId));
return result;
}
@Override
public ParcelFileDescriptor openDocument(String documentId, String mode, @Nullable CancellationSignal signal) throws FileNotFoundException {
File file = new File(documentId);
var file = new File(documentId);
int imode = ParcelFileDescriptor.parseMode(mode);
return ParcelFileDescriptor.open(file, imode);
}
@Override
public String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
File file = new File(parentDocumentId, displayName);
var file = new File(parentDocumentId, displayName);
if (file.exists()) {
int nextId = 1;
while (file.exists()) {
// maybe let's put the id before extension?
file = new File(parentDocumentId, String.format("%s (%d)", displayName, nextId));
file = new File(parentDocumentId, String.format(Locale.US, "%s (%d)", displayName, nextId));
++nextId;
}
@@ -146,36 +147,37 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
} catch (IOException e) {
throw new FileNotFoundException("Couldn't create file: " + e.getMessage());
}
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
var uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
var resolver = Objects.requireNonNull(getContext()).getContentResolver();
if (SDK_INT >= Build.VERSION_CODES.R) {
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_INSERT);
resolver.notifyChange(uri, null, ContentResolver.NOTIFY_INSERT);
} else {
getContext().getContentResolver().notifyChange(uri, null);
resolver.notifyChange(uri, null);
}
return file.getAbsolutePath();
}
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
File file = new File(documentId);
var file = new File(documentId);
if (!file.exists()) {
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
}
deleteRecursive(file);
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
var uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
var resolver = Objects.requireNonNull(getContext()).getContentResolver();
if (SDK_INT >= Build.VERSION_CODES.R) {
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_DELETE);
resolver.notifyChange(uri, null, ContentResolver.NOTIFY_DELETE);
} else {
getContext().getContentResolver().notifyChange(uri, null);
resolver.notifyChange(uri, null);
}
}
@@ -183,17 +185,17 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
@RequiresApi(Build.VERSION_CODES.O)
public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) throws FileNotFoundException {
if (parentDocumentId == null) {
parentDocumentId = getContext().getFilesDir().getAbsolutePath();
parentDocumentId = Objects.requireNonNull(getContext()).getFilesDir().getAbsolutePath();
}
File childFile = new File(childDocumentId);
var childFile = new File(childDocumentId);
if (!childFile.exists()) {
throw new FileNotFoundException(childFile.getAbsolutePath()+" doesn't exist");
throw new FileNotFoundException(childFile.getAbsolutePath() + " doesn't exist");
} else if (!isChildDocument(parentDocumentId, childDocumentId)) {
throw new FileNotFoundException(childDocumentId+" is not child of "+parentDocumentId);
throw new FileNotFoundException(childDocumentId + " is not child of " + parentDocumentId);
}
LinkedList<String> path = new LinkedList<>();
var path = new LinkedList<String>();
while (childFile != null && isChildDocument(parentDocumentId, childFile.getAbsolutePath())) {
path.addFirst(childFile.getAbsolutePath());
childFile = childFile.getParentFile();
@@ -204,7 +206,7 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
@Override
public String getDocumentType(String documentId) throws FileNotFoundException {
File file = new File(documentId);
var file = new File(documentId);
if (!file.exists()) {
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
@@ -229,13 +231,13 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
@Override
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
File file = new File(documentId);
var file = new File(documentId);
if (!file.exists()) {
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
}
File newPath = new File(file.getParentFile().getAbsolutePath() + "/" + displayName);
var newPath = new File(file.getParentFile().getAbsolutePath() + "/" + displayName);
try {
if (SDK_INT >= Build.VERSION_CODES.O) {
@@ -249,15 +251,15 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
throw new FileNotFoundException(e.getMessage());
}
var uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, file.getParent());
var resolver = Objects.requireNonNull(getContext()).getContentResolver();
if (SDK_INT >= Build.VERSION_CODES.R) {
getContext().getContentResolver().notifyChange(uri, null, ContentResolver.NOTIFY_UPDATE);
resolver.notifyChange(uri, null, ContentResolver.NOTIFY_UPDATE);
} else {
getContext().getContentResolver().notifyChange(uri, null);
resolver.notifyChange(uri, null);
}
return newPath.getAbsolutePath();
}
@@ -268,7 +270,7 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
private static void deleteRecursive(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
var files = file.listFiles();
if (files != null) {
for (File f : files) {
if (SDK_INT >= Build.VERSION_CODES.O) {
@@ -294,20 +296,14 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
private static String getMimeType(String url) {
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(url.toLowerCase());
var extension = MimeTypeMap.getFileExtensionFromUrl(url.toLowerCase());
if (extension != null) {
switch (extension) {
case "pbm":
type = "image/bmp";
break;
case "yml":
type = "text/x-yaml";
break;
default:
type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
break;
}
type = switch (extension) {
case "pbm" -> "image/bmp";
case "yml" -> "text/x-yaml";
default -> MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
};
}
if (type == null) {
@@ -322,7 +318,7 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
throw new FileNotFoundException("Couldn't find file: " + file.getAbsolutePath());
}
String mimeType = "application/octet-stream";
var mimeType = "application/octet-stream";
int flags = 0;
if (file.isDirectory()) {
@@ -339,7 +335,8 @@ public class DoukutsuDocumentsProvider extends DocumentsProvider {
}
}
if (file.getParentFile().canWrite()) {
var parent = file.getParentFile();
if (parent != null && parent.canWrite()) {
flags |= Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
}
@@ -6,12 +6,20 @@ import android.os.Bundle;
import android.os.Handler;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.*;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -22,7 +30,7 @@ public class DownloadActivity extends AppCompatActivity {
private ProgressBar progressBar;
private DownloadThread downloadThread;
private String basePath;
private Handler handler = new Handler();
private final Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -48,15 +56,15 @@ public class DownloadActivity extends AppCompatActivity {
private class DownloadThread extends Thread {
private final ArrayList<DownloadEntry> urls = new ArrayList<>();
private final ArrayList<String> filesWhitelist = new ArrayList<>();
private final List<String> filesWhitelist = List.of(
"data/",
"Doukutsu.exe"
);
@Override
public void run() {
this.filesWhitelist.add("data/");
this.filesWhitelist.add("Doukutsu.exe");
// DON'T SET `true` VALUE FOR TRANSLATIONS
this.urls.add(new DownloadEntry(R.string.download_entries_base, "https://www.cavestory.org/downloads/cavestoryen.zip", true));
this.urls.add(new DownloadEntry(getString(R.string.download_entries_base), "https://www.cavestory.org/downloads/cavestoryen.zip", true));
for (DownloadEntry entry : this.urls) {
this.download(entry);
@@ -66,7 +74,7 @@ public class DownloadActivity extends AppCompatActivity {
private void download(DownloadEntry downloadEntry) {
HttpURLConnection connection = null;
try {
URL url = new URL(downloadEntry.url);
var url = new URL(downloadEntry.url);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
@@ -86,19 +94,19 @@ public class DownloadActivity extends AppCompatActivity {
int downloadedLast = 0;
int downloaded = 0;
byte[] buffer = new byte[4096];
var buffer = new byte[4096];
int count;
long last = System.currentTimeMillis();
var last = System.currentTimeMillis();
while ((count = input.read(buffer)) != -1) {
downloaded += count;
output.write(buffer, 0, count);
long now = System.currentTimeMillis();
var now = System.currentTimeMillis();
if (last + 1000 >= now) {
int speed = (int) ((downloaded - downloadedLast) / 1024.0);
String text = (fileLength > 0)
var speed = (int) ((downloaded - downloadedLast) / 1024.0);
var text = (fileLength > 0)
? getString(R.string.download_status_downloading, downloadEntry.name, downloaded * 100 / fileLength, downloaded / 1024, fileLength / 1024, speed)
: getString(R.string.download_status_downloading_null, downloadEntry.name, downloaded / 1024, speed);
@@ -114,7 +122,8 @@ public class DownloadActivity extends AppCompatActivity {
output.close();
}
new File(basePath).mkdirs();
@SuppressWarnings("unused")
var _created = new File(basePath).mkdirs();
this.unpack(zipFile, downloadEntry.isBase);
handler.post(() -> txtProgress.setText(getString(R.string.download_status_done)));
@@ -140,51 +149,53 @@ public class DownloadActivity extends AppCompatActivity {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
ZipPathValidator.clearCallback();
}
ZipInputStream in = new ZipInputStream(new ByteArrayInputStream(zipFile));
ZipEntry entry;
byte[] buffer = new byte[4096];
while ((entry = in.getNextEntry()) != null) {
String entryName = entry.getName();
try (var in = new ZipInputStream(new ByteArrayInputStream(zipFile))) {
ZipEntry entry;
var buffer = new byte[4096];
while ((entry = in.getNextEntry()) != null) {
var entryName = entry.getName();
// https://developer.android.com/privacy-and-security/risks/zip-path-traversal
if (entryName.contains("..") || entryName.startsWith("/")) {
in.closeEntry();
continue;
}
// https://developer.android.com/privacy-and-security/risks/zip-path-traversal
if (entryName.contains("..") || entryName.startsWith("/")) {
in.closeEntry();
continue;
}
// strip prefix
if (entryName.startsWith("CaveStory/")) {
entryName = entryName.substring("CaveStory/".length());
}
// strip prefix
if (entryName.startsWith("CaveStory/")) {
entryName = entryName.substring("CaveStory/".length());
}
if (!this.entryInWhitelist(entryName)) {
in.closeEntry();
continue;
}
if (!this.entryInWhitelist(entryName)) {
in.closeEntry();
continue;
}
final String s = entryName;
handler.post(() -> txtProgress.setText(
getString(R.string.download_status_unpacking, s)
));
final var s = entryName;
handler.post(() -> txtProgress.setText(
getString(R.string.download_status_unpacking, s)
));
if (entry.isDirectory()) {
new File(basePath + entryName).mkdirs();
} else {
try (FileOutputStream fos = new FileOutputStream(basePath + entryName)) {
int count;
while ((count = in.read(buffer)) != -1) {
fos.write(buffer, 0, count);
if (entry.isDirectory()) {
@SuppressWarnings("unused")
var _created = new File(basePath + entryName).mkdirs();
} else {
try (var fos = new FileOutputStream(basePath + entryName)) {
int count;
while ((count = in.read(buffer)) != -1) {
fos.write(buffer, 0, count);
}
}
}
}
in.closeEntry();
in.closeEntry();
}
}
}
private boolean entryInWhitelist(String entry) {
for (String file : this.filesWhitelist) {
for (var file : this.filesWhitelist) {
if (entry.startsWith(file)) {
return true;
}
@@ -194,21 +205,14 @@ public class DownloadActivity extends AppCompatActivity {
}
}
private class DownloadEntry {
public String name; //e.g. "Polish translation", "Base data files"
public String url;
public boolean isBase = false;
DownloadEntry(String name, String url, boolean isBase) {
this.name = name;
this.url = url;
this.isBase = isBase;
}
DownloadEntry(int name, String url, boolean isBase) {
this.name = getString(name);
this.url = url;
this.isBase = isBase;
}
/**
* Record class for download entries.
*
* @param name The display name of the download entry.
* @param url The URL to download the entry from.
* @param isBase Indicates if this entry is the base data files. true if the entry is for the
* base data files, false for an overlay such as a translation.
*/
private record DownloadEntry(String name, String url, boolean isBase) {
}
}
@@ -1,22 +1,20 @@
package io.github.doukutsu_rs;
import static android.os.Build.VERSION.SDK_INT;
import android.app.NativeActivity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.Configuration;
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.view.OrientationEventListener;
import android.view.WindowInsets;
import android.widget.Toast;
import java.io.File;
import static android.os.Build.VERSION.SDK_INT;
public class GameActivity extends NativeActivity {
private int[] displayInsets = new int[]{0, 0, 0, 0};
private OrientationEventListener listener;
@@ -72,7 +70,7 @@ public class GameActivity extends NativeActivity {
this.displayInsets[2] = 0;
this.displayInsets[3] = 0;
WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
var insets = getWindow().getDecorView().getRootWindowInsets();
if (insets != null) {
this.displayInsets[0] = Math.max(this.displayInsets[0], insets.getStableInsetLeft());
@@ -84,7 +82,7 @@ public class GameActivity extends NativeActivity {
}
if (SDK_INT >= Build.VERSION_CODES.P) {
android.view.DisplayCutout cutout = insets.getDisplayCutout();
var cutout = insets.getDisplayCutout();
if (cutout != null) {
this.displayInsets[0] = Math.max(this.displayInsets[0], cutout.getSafeInsetLeft());
@@ -96,22 +94,22 @@ public class GameActivity extends NativeActivity {
}
public void openDir(String path) {
Uri uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, path);
var uri = DocumentsContract.buildDocumentUri(BuildConfig.DOCUMENTS_AUTHORITY, path);
File file = new File(path);
var file = new File(path);
if (!file.isDirectory()) {
Toast.makeText(getApplicationContext(), R.string.dir_not_found, Toast.LENGTH_LONG).show();
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
var intent = new Intent(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setDataAndType(uri, DocumentsContract.Document.MIME_TYPE_DIR);
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
try {
startActivity(intent);
} catch(ActivityNotFoundException e) {
} catch (ActivityNotFoundException e) {
Toast.makeText(getApplicationContext(), R.string.no_app_found_to_open_dir, Toast.LENGTH_LONG).show();
}
}
@@ -3,6 +3,7 @@ package io.github.doukutsu_rs;
import android.app.AlertDialog;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
@@ -16,8 +17,8 @@ public class MainActivity extends AppCompatActivity {
ActivityUtils.hideSystemBars(this);
File f = new File(getFilesDir().getAbsolutePath() + "/data/");
String[] list = f.list();
var f = new File(getFilesDir().getAbsolutePath() + "/data/");
var list = f.list();
if (!f.exists() || (list != null && list.length == 0)) {
messageBox(getString(R.string.missing_data_title), getString(R.string.missing_data_desc), () -> {
Intent intent = new Intent(this, DownloadActivity.class);
@@ -31,7 +32,7 @@ public class MainActivity extends AppCompatActivity {
}
private void launchGame() {
Intent intent = new Intent(this, GameActivity.class);
var intent = new Intent(this, GameActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
this.finish();
@@ -39,7 +40,7 @@ public class MainActivity extends AppCompatActivity {
private void messageBox(String title, String message, Runnable yesCallback, Runnable noCallback) {
this.runOnUiThread(() -> {
AlertDialog.Builder alert = new AlertDialog.Builder(this);
var alert = new AlertDialog.Builder(this);
alert.setTitle(title);
alert.setMessage(message);
alert.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> yesCallback.run());