almost done with kotlin migration
Signed-off-by: androidacy-user <opensource@androidacy.com>pull/89/head
parent
547a509e64
commit
8de65a47f6
@ -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…
Reference in New Issue