almost done with kotlin migration

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/89/head
androidacy-user 2 years ago
parent 547a509e64
commit 8de65a47f6

@ -51,6 +51,7 @@ import com.fox2code.mmm.repo.RepoModule
import com.fox2code.mmm.settings.SettingsActivity
import com.fox2code.mmm.utils.ExternalHelper
import com.fox2code.mmm.utils.RuntimeUtils
import com.fox2code.mmm.utils.SyncManager
import com.fox2code.mmm.utils.io.net.Http.Companion.cleanDnsCache
import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView
import com.fox2code.mmm.utils.realm.ReposList
@ -388,17 +389,20 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
if (BuildConfig.DEBUG) Timber.i("Check Update")
// update repos
if (hasWebView()) {
RepoManager.getINSTANCE()!!.update { value: Double ->
runOnUiThread(if (max == 0) Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION).toInt(), true
)
} else Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION * 0.75f).toInt(), true
)
})
val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread(if (max == 0) Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION).toInt(), true
)
} else Runnable {
progressIndicator.setProgressCompat(
(value * PRECISION * 0.75f).toInt(), true
)
})
}
}
RepoManager.getINSTANCE()!!.update(updateListener)
}
// various notifications
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder)
@ -606,13 +610,16 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
progressIndicator!!.max = PRECISION
}
if (BuildConfig.DEBUG) Timber.i("Check Update")
RepoManager.getINSTANCE()!!.update { value: Double ->
runOnUiThread {
progressIndicator!!.setProgressCompat(
(value * PRECISION).toInt(), true
)
val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread {
progressIndicator!!.setProgressCompat(
(value * PRECISION).toInt(), true
)
}
}
}
RepoManager.getINSTANCE()!!.update(updateListener)
runOnUiThread {
progressIndicator!!.setProgressCompat(PRECISION, true)
progressIndicator!!.visibility = View.GONE
@ -647,17 +654,20 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis
Thread({
cleanDnsCache() // Allow DNS reload from network
val max = instance!!.getUpdatableModuleCount()
RepoManager.getINSTANCE()!!.update { value: Double ->
runOnUiThread(if (max == 0) Runnable {
progressIndicator!!.setProgressCompat(
(value * PRECISION).toInt(), true
)
} else Runnable {
progressIndicator!!.setProgressCompat(
(value * PRECISION * 0.75f).toInt(), true
)
})
val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener {
override fun update(value: Double) {
runOnUiThread(if (max == 0) Runnable {
progressIndicator!!.setProgressCompat(
(value * PRECISION).toInt(), true
)
} else Runnable {
progressIndicator!!.setProgressCompat(
(value * PRECISION * 0.75f).toInt(), true
)
})
}
}
RepoManager.getINSTANCE()!!.update(updateListener)
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder)
if (!NotificationType.NO_INTERNET.shouldRemove()) {
moduleViewListBuilderOnline.addNotification(NotificationType.NO_INTERNET)

@ -1,29 +1,32 @@
/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm.utils
package com.fox2code.mmm.utils;
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import com.fox2code.mmm.MainActivity
import java.util.concurrent.ThreadLocalRandom
import kotlin.system.exitProcess
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import com.fox2code.mmm.MainActivity;
import java.util.concurrent.ThreadLocalRandom;
public enum ProcessHelper {
enum class ProcessHelper {
;
private static final int sPendingIntentId = ThreadLocalRandom.current().nextInt(100, 1000000 + 1);
public static void restartApplicationProcess(Context context) {
Intent mStartActivity = new Intent(context, MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent mPendingIntent = PendingIntent.getActivity(context, sPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0); // Exit app process
companion object {
private val sPendingIntentId = ThreadLocalRandom.current().nextInt(100, 1000000 + 1)
@JvmStatic
fun restartApplicationProcess(context: Context) {
val mStartActivity = Intent(context, MainActivity::class.java)
mStartActivity.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
val mPendingIntent = PendingIntent.getActivity(
context, sPendingIntentId,
mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
mgr[AlarmManager.RTC, System.currentTimeMillis() + 100] = mPendingIntent
exitProcess(0) // Exit app process
}
}
}

@ -1,88 +1,79 @@
/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm.utils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
package com.fox2code.mmm.utils
/**
* Manager that want both to be thread safe and not to worry about thread safety
* {@link #scan()} and {@link #update(UpdateListener)} can be called from multiple
* thread at the same time, {@link #scanInternal(UpdateListener)} will only be
* [.scan] and [.update] can be called from multiple
* thread at the same time, [.scanInternal] will only be
* called from one thread at a time only.
*/
public abstract class SyncManager {
private static final UpdateListener NO_OP = value -> {
};
protected final Object syncLock = new Object();
private boolean syncing;
private long lastSync;
public final void scanAsync() {
if (!this.syncing) {
new Thread(this::scan, "Scan Thread").start();
abstract class SyncManager {
protected val syncLock = Any()
private var syncing = false
private var lastSync: Long = 0
fun scanAsync() {
if (!syncing) {
Thread({ this.scan() }, "Scan Thread").start()
}
}
public final void scan() {
this.update(null);
fun scan() {
update(null)
}
// MultiThread friendly method
public final void update(@Nullable UpdateListener updateListener) {
if (updateListener == null) updateListener = NO_OP;
if (!this.syncing) {
@Suppress("NAME_SHADOWING")
fun update(updateListener: UpdateListener?) {
var updateListener = updateListener
if (updateListener == null) updateListener = NO_OP
if (!syncing) {
// Do scan
synchronized (this.syncLock) {
if (System.currentTimeMillis() < this.lastSync + 50L)
return; // Skip sync if it was synced too recently
this.syncing = true;
synchronized(syncLock) {
if (System.currentTimeMillis() < lastSync + 50L) return // Skip sync if it was synced too recently
syncing = true
try {
this.scanInternal(updateListener);
scanInternal(updateListener)
} finally {
this.lastSync = System.currentTimeMillis();
this.syncing = false;
lastSync = System.currentTimeMillis()
syncing = false
}
}
} else {
// Wait for current scan
synchronized (this.syncLock) {
Thread.yield();
}
synchronized(syncLock) { Thread.yield() }
}
}
// Pause execution until the scan is completed if one is currently running
public final void afterScan() {
if (this.syncing) synchronized (this.syncLock) {
Thread.yield();
}
fun afterScan() {
if (syncing) synchronized(syncLock) { Thread.yield() }
}
public final void runAfterScan(Runnable runnable) {
synchronized (this.syncLock) {
runnable.run();
}
fun runAfterScan(runnable: Runnable) {
synchronized(syncLock) { runnable.run() }
}
public final void afterUpdate() {
if (this.syncing) synchronized (this.syncLock) {
Thread.yield();
}
fun afterUpdate() {
if (syncing) synchronized(syncLock) { Thread.yield() }
}
public final void runAfterUpdate(Runnable runnable) {
synchronized (this.syncLock) {
runnable.run();
}
fun runAfterUpdate(runnable: Runnable) {
synchronized(syncLock) { runnable.run() }
}
// This method can't be called twice at the same time.
protected abstract void scanInternal(@NonNull UpdateListener updateListener);
protected abstract fun scanInternal(updateListener: UpdateListener)
interface UpdateListener {
fun update(value: Double)
}
public interface UpdateListener {
void update(double value);
companion object {
private val NO_OP: UpdateListener = object : UpdateListener {
override fun update(value: Double) {
}
}
}
}

@ -1,190 +1,199 @@
/*
* Copyright (c) 2023 to present Androidacy and contributors. Names, logos, icons, and the Androidacy name are all trademarks of Androidacy and may not be used without license. See LICENSE for more information.
*/
package com.fox2code.mmm.utils
package com.fox2code.mmm.utils;
import android.content.DialogInterface
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.fox2code.foxcompat.app.FoxActivity
import com.fox2code.mmm.BuildConfig
import com.fox2code.mmm.R
import com.fox2code.mmm.installer.InstallerInitializer.Companion.peekMagiskPath
import com.fox2code.mmm.utils.BudgetProgressDialog.Companion.build
import com.fox2code.mmm.utils.IntentHelper.Companion.openInstaller
import com.fox2code.mmm.utils.io.Files.Companion.getFileName
import com.fox2code.mmm.utils.io.Files.Companion.getFileSize
import com.fox2code.mmm.utils.io.PropUtils.Companion.readModulePropSimple
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.mmm.BuildConfig;
import com.fox2code.mmm.R;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.utils.io.Files;
import com.fox2code.mmm.utils.io.PropUtils;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import timber.log.Timber;
public class ZipFileOpener extends FoxActivity {
AlertDialog loading = null;
class ZipFileOpener : FoxActivity() {
var loading: AlertDialog? = null
// Adds us as a handler for zip files, so we can pass them to the installer
// We should have a content uri provided to us.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loading = BudgetProgressDialog.build(this, R.string.loading, R.string.zip_unpacking);
new Thread(() -> {
Timber.d("onCreate: %s", getIntent());
File zipFile;
Uri uri = getIntent().getData();
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loading = build(this, R.string.loading, R.string.zip_unpacking)
Thread(Runnable {
Timber.d("onCreate: %s", intent)
val zipFile: File
val uri = intent.data
if (uri == null) {
Timber.e("onCreate: No data provided");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
return;
Timber.e("onCreate: No data provided")
runOnUiThread {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show()
finishAndRemoveTask()
}
return@Runnable
}
// Try to copy the file to our cache
try {
// check if its a file over 10MB
Long fileSize = Files.getFileSize(this, uri);
if (fileSize == null) fileSize = 0L;
var fileSize = getFileSize(this, uri)
if (fileSize == null) fileSize = 0L
if (1000L * 1000 * 10 < fileSize) {
runOnUiThread(() -> loading.show());
runOnUiThread { loading!!.show() }
}
zipFile = File.createTempFile("module", ".zip", getCacheDir());
try (InputStream inputStream = getContentResolver().openInputStream(uri); FileOutputStream outputStream = new FileOutputStream(zipFile)) {
if (inputStream == null) {
Timber.e("onCreate: Failed to open input stream");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
return;
}
byte[] buffer = new byte[4096];
int read;
while ((read = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
zipFile = File.createTempFile("module", ".zip", cacheDir)
contentResolver.openInputStream(uri).use { inputStream ->
FileOutputStream(zipFile).use { outputStream ->
if (inputStream == null) {
Timber.e("onCreate: Failed to open input stream")
runOnUiThread {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG)
.show()
finishAndRemoveTask()
}
return@Runnable
}
val buffer = ByteArray(4096)
var read: Int
while (inputStream.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
}
}
} catch (
Exception e) {
Timber.e(e, "onCreate: Failed to copy zip file");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
return;
} catch (e: Exception) {
Timber.e(e, "onCreate: Failed to copy zip file")
runOnUiThread {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show()
finishAndRemoveTask()
}
return@Runnable
}
// Ensure zip is not empty
if (zipFile.length() == 0) {
Timber.e("onCreate: Zip file is empty");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
return;
if (zipFile.length() == 0L) {
Timber.e("onCreate: Zip file is empty")
runOnUiThread {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show()
finishAndRemoveTask()
}
return@Runnable
} else {
Timber.d("onCreate: Zip file is " + zipFile.length() + " bytes");
Timber.d("onCreate: Zip file is " + zipFile.length() + " bytes")
}
ZipEntry entry;
ZipFile zip = null;
var entry: ZipEntry?
var zip: ZipFile? = null
// Unpack the zip to validate it's a valid magisk module
// It needs to have, at the bare minimum, a module.prop file. Everything else is technically optional.
// First, check if it's a zip file
try {
zip = new ZipFile(zipFile);
if ((entry = zip.getEntry("module.prop")) == null) {
Timber.e("onCreate: Zip file is not a valid magisk module");
zip = ZipFile(zipFile)
if (zip.getEntry("module.prop").also { entry = it } == null) {
Timber.e("onCreate: Zip file is not a valid magisk module")
if (BuildConfig.DEBUG) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Timber.d("onCreate: Zip file contents: %s", zip.stream().map(ZipEntry::getName).reduce((a, b) -> a + ", " + b).orElse("empty"));
Timber.d(
"onCreate: Zip file contents: %s",
zip.stream().map { obj: ZipEntry -> obj.name }
.reduce { a: String, b: String -> "$a, $b" }.orElse("empty")
)
} else {
Timber.d("onCreate: Zip file contents cannot be listed on this version of android");
Timber.d("onCreate: Zip file contents cannot be listed on this version of android")
}
}
runOnUiThread(() -> {
Toast.makeText(this, R.string.invalid_format, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
return;
runOnUiThread {
Toast.makeText(this, R.string.invalid_format, Toast.LENGTH_LONG).show()
finishAndRemoveTask()
}
return@Runnable
}
} catch (e: Exception) {
Timber.e(e, "onCreate: Failed to open zip file")
runOnUiThread {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show()
finishAndRemoveTask()
}
} catch (
Exception e) {
Timber.e(e, "onCreate: Failed to open zip file");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
if (zip != null) {
try {
zip.close();
} catch (IOException exception) {
Timber.e(Log.getStackTraceString(exception));
zip.close()
} catch (exception: IOException) {
Timber.e(Log.getStackTraceString(exception))
}
}
return;
return@Runnable
}
Timber.d("onCreate: Zip file is valid");
String moduleInfo;
Timber.d("onCreate: Zip file is valid")
var moduleInfo: String?
try {
moduleInfo = PropUtils.readModulePropSimple(zip.getInputStream(entry), "name");
moduleInfo = readModulePropSimple(zip.getInputStream(entry), "name")
if (moduleInfo == null) {
moduleInfo = PropUtils.readModulePropSimple(zip.getInputStream(entry), "id");
moduleInfo = readModulePropSimple(zip.getInputStream(entry), "id")
}
if (moduleInfo == null) {
throw new NullPointerException("moduleInfo is null, check earlier logs for root cause");
throw NullPointerException("moduleInfo is null, check earlier logs for root cause")
}
} catch (e: Exception) {
Timber.e(e, "onCreate: Failed to load module id")
runOnUiThread {
Toast.makeText(this, R.string.zip_prop_load_failed, Toast.LENGTH_LONG).show()
finishAndRemoveTask()
}
} catch (
Exception e) {
Timber.e(e, "onCreate: Failed to load module id");
runOnUiThread(() -> {
Toast.makeText(this, R.string.zip_prop_load_failed, Toast.LENGTH_LONG).show();
finishAndRemoveTask();
});
try {
zip.close();
} catch (IOException exception) {
Timber.e(Log.getStackTraceString(exception));
zip.close()
} catch (exception: IOException) {
Timber.e(Log.getStackTraceString(exception))
}
return;
return@Runnable
}
try {
zip.close();
} catch (IOException exception) {
Timber.e(Log.getStackTraceString(exception));
zip.close()
} catch (exception: IOException) {
Timber.e(Log.getStackTraceString(exception))
}
val finalModuleInfo: String = moduleInfo
runOnUiThread {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.zip_security_warning, finalModuleInfo))
.setMessage(
getString(
R.string.zip_intent_module_install,
finalModuleInfo,
getFileName(this, uri)
)
)
.setCancelable(false)
.setNegativeButton(R.string.no) { d: DialogInterface, _: Int ->
d.dismiss()
finishAndRemoveTask()
}
.setPositiveButton(R.string.yes) { d: DialogInterface, _: Int ->
d.dismiss()
// Pass the file to the installer
val compatActivity = getFoxActivity(this)
openInstaller(
compatActivity, zipFile.absolutePath,
compatActivity.getString(
R.string.local_install_title
), null, null, false,
BuildConfig.DEBUG && // Use debug mode if no root
peekMagiskPath() == null
)
finish()
}
.show()
loading!!.dismiss()
}
String finalModuleInfo = moduleInfo;
runOnUiThread(() -> {
new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.zip_security_warning, finalModuleInfo))
.setMessage(getString(R.string.zip_intent_module_install, finalModuleInfo, Files.getFileName(this, uri)))
.setCancelable(false)
.setNegativeButton(R.string.no, (d, i) -> {
d.dismiss();
finishAndRemoveTask();
})
.setPositiveButton(R.string.yes, (d, i) -> {
d.dismiss();
// Pass the file to the installer
FoxActivity compatActivity = FoxActivity.getFoxActivity(this);
IntentHelper.openInstaller(compatActivity, zipFile.getAbsolutePath(),
compatActivity.getString(
R.string.local_install_title), null, null, false,
BuildConfig.DEBUG && // Use debug mode if no root
InstallerInitializer.peekMagiskPath() == null);
finish();
})
.show();
loading.dismiss();
});
}).start();
}).start()
}
}
Loading…
Cancel
Save