parent
e36c36821a
commit
833ecd081d
@ -1,123 +1,115 @@
|
||||
package com.fox2code.mmm.utils
|
||||
package com.fox2code.mmm.utils;
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import androidx.core.util.Supplier
|
||||
import com.fox2code.mmm.Constants
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler
|
||||
import timber.log.Timber
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
class ExternalHelper private constructor() {
|
||||
private var fallback: ComponentName? = null
|
||||
private var label: CharSequence? = null
|
||||
private var multi = false
|
||||
fun refreshHelper(context: Context) {
|
||||
val intent = Intent(
|
||||
FOX_MMM_OPEN_EXTERNAL,
|
||||
Uri.parse("https://very-invalid-prefix-for-testing.androidacy.com")
|
||||
)
|
||||
@Suppress("DEPRECATION") val resolveInfos =
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER)
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.core.util.Supplier;
|
||||
|
||||
import com.fox2code.mmm.Constants;
|
||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public final class ExternalHelper {
|
||||
public static final ExternalHelper INSTANCE = new ExternalHelper();
|
||||
private static final boolean TEST_MODE = false;
|
||||
private static final String FOX_MMM_OPEN_EXTERNAL = "com.fox2code.mmm.utils.intent.action.OPEN_EXTERNAL";
|
||||
private static final String FOX_MMM_EXTRA_REPO_ID = "extra_repo_id";
|
||||
private ComponentName fallback;
|
||||
private CharSequence label;
|
||||
private boolean multi;
|
||||
|
||||
private ExternalHelper() {
|
||||
}
|
||||
|
||||
public void refreshHelper(Context context) {
|
||||
Intent intent = new Intent(FOX_MMM_OPEN_EXTERNAL, Uri.parse("https://fox2code.com/module.zip"));
|
||||
List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
|
||||
if (resolveInfos.isEmpty()) {
|
||||
Timber.i("No external provider installed!")
|
||||
label = if (TEST_MODE) "External" else null
|
||||
multi = TEST_MODE
|
||||
fallback = null
|
||||
Timber.i("No external provider installed!");
|
||||
label = TEST_MODE ? "External" : null;
|
||||
multi = TEST_MODE;
|
||||
fallback = null;
|
||||
} else {
|
||||
val resolveInfo = resolveInfos[0]
|
||||
Timber.i("Found external provider: %s", resolveInfo.activityInfo.packageName)
|
||||
fallback =
|
||||
ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name)
|
||||
label = resolveInfo.loadLabel(context.packageManager)
|
||||
multi = resolveInfos.size >= 2
|
||||
ResolveInfo resolveInfo = resolveInfos.get(0);
|
||||
Timber.i("Found external provider: %s", resolveInfo.activityInfo.packageName);
|
||||
fallback = new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
|
||||
label = resolveInfo.loadLabel(context.getPackageManager());
|
||||
multi = resolveInfos.size() >= 2;
|
||||
}
|
||||
}
|
||||
|
||||
fun openExternal(context: Context, uri: Uri?, repoId: String?): Boolean {
|
||||
if (label == null) return false
|
||||
val param =
|
||||
ActivityOptionsCompat.makeCustomAnimation(context, com.google.android.material.R.anim.abc_fade_in, com.google.android.material.R.anim.abc_fade_out).toBundle()
|
||||
var intent = Intent(FOX_MMM_OPEN_EXTERNAL, uri)
|
||||
intent.flags = IntentHelper.FLAG_GRANT_URI_PERMISSION
|
||||
intent.putExtra(FOX_MMM_EXTRA_REPO_ID, repoId)
|
||||
public boolean openExternal(Context context, Uri uri, String repoId) {
|
||||
if (label == null)
|
||||
return false;
|
||||
Bundle param = ActivityOptionsCompat.makeCustomAnimation(context, android.R.anim.fade_in, android.R.anim.fade_out).toBundle();
|
||||
Intent intent = new Intent(FOX_MMM_OPEN_EXTERNAL, uri);
|
||||
intent.setFlags(IntentHelper.FLAG_GRANT_URI_PERMISSION);
|
||||
intent.putExtra(FOX_MMM_EXTRA_REPO_ID, repoId);
|
||||
if (multi) {
|
||||
intent = Intent.createChooser(intent, label)
|
||||
intent = Intent.createChooser(intent, label);
|
||||
} else {
|
||||
intent.putExtra(Constants.EXTRA_FADE_OUT, true)
|
||||
intent.putExtra(Constants.EXTRA_FADE_OUT, true);
|
||||
}
|
||||
try {
|
||||
if (multi) {
|
||||
context.startActivity(intent)
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
context.startActivity(intent, param)
|
||||
context.startActivity(intent, param);
|
||||
}
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Timber.e(e)
|
||||
return true;
|
||||
} catch (
|
||||
ActivityNotFoundException e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
if (fallback != null) {
|
||||
if (multi) {
|
||||
intent = Intent(FOX_MMM_OPEN_EXTERNAL, uri)
|
||||
intent.putExtra(FOX_MMM_EXTRA_REPO_ID, repoId)
|
||||
intent.putExtra(Constants.EXTRA_FADE_OUT, true)
|
||||
intent = new Intent(FOX_MMM_OPEN_EXTERNAL, uri);
|
||||
intent.putExtra(FOX_MMM_EXTRA_REPO_ID, repoId);
|
||||
intent.putExtra(Constants.EXTRA_FADE_OUT, true);
|
||||
}
|
||||
intent.component = fallback
|
||||
intent.setComponent(fallback);
|
||||
try {
|
||||
context.startActivity(intent, param)
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Timber.e(e)
|
||||
context.startActivity(intent, param);
|
||||
return true;
|
||||
} catch (
|
||||
ActivityNotFoundException e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
fun injectButton(builder: AlertDialog.Builder, uriSupplier: Supplier<Uri?>, repoId: String?) {
|
||||
if (label == null) return
|
||||
builder.setNeutralButton(label) { dialog: DialogInterface, _: Int ->
|
||||
val context = (dialog as Dialog).context
|
||||
object : Thread("Async downloader") {
|
||||
override fun run() {
|
||||
val uri = uriSupplier.get()
|
||||
if (uri == null) {
|
||||
UiThreadHandler.run {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Failed to get uri",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return
|
||||
}
|
||||
UiThreadHandler.run {
|
||||
public void injectButton(AlertDialog.Builder builder, Supplier<Uri> uriSupplier, String repoId) {
|
||||
if (label == null)
|
||||
return;
|
||||
builder.setNeutralButton(label, (dialog, button) -> {
|
||||
Context context = ((Dialog) dialog).getContext();
|
||||
new Thread("Async downloader") {
|
||||
@Override
|
||||
public void run() {
|
||||
final Uri uri = uriSupplier.get();
|
||||
if (uri == null)
|
||||
return;
|
||||
UiThreadHandler.run(() -> {
|
||||
if (!openExternal(context, uri, repoId)) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Failed to launch external activity",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Toast.makeText(context, "Failed to launch external activity", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val INSTANCE = ExternalHelper()
|
||||
private const val TEST_MODE = false
|
||||
private const val FOX_MMM_OPEN_EXTERNAL =
|
||||
"com.fox2code.mmm.utils.intent.action.OPEN_EXTERNAL"
|
||||
private const val FOX_MMM_EXTRA_REPO_ID = "extra_repo_id"
|
||||
}.start();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
package com.fox2code.mmm.utils
|
||||
package com.fox2code.mmm.utils;
|
||||
|
||||
class FastException private constructor() : RuntimeException() {
|
||||
@Synchronized
|
||||
override fun fillInStackTrace(): Throwable {
|
||||
return this
|
||||
}
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public final class FastException extends RuntimeException {
|
||||
public static final FastException INSTANCE = new FastException();
|
||||
|
||||
private FastException() {}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val INSTANCE = FastException()
|
||||
@NonNull
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,25 @@
|
||||
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;
|
||||
|
||||
enum class ProcessHelper {
|
||||
import com.fox2code.mmm.MainActivity;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public enum ProcessHelper {
|
||||
;
|
||||
private static final int sPendingIntentId = ThreadLocalRandom.current().nextInt(100, 1000000 + 1);
|
||||
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +1,79 @@
|
||||
package com.fox2code.mmm.utils
|
||||
package com.fox2code.mmm.utils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Manager that want both to be thread safe and not to worry about thread safety
|
||||
* [.scan] and [.update] can be called from multiple
|
||||
* thread at the same time, [.scanInternal] will only be
|
||||
* {@link #scan()} and {@link #update(UpdateListener)} can be called from multiple
|
||||
* thread at the same time, {@link #scanInternal(UpdateListener)} will only be
|
||||
* called from one thread at a time only.
|
||||
*/
|
||||
abstract class SyncManager {
|
||||
@JvmField
|
||||
protected val syncLock = Any()
|
||||
private var syncing = false
|
||||
private var lastSync: Long = 0
|
||||
fun scanAsync() {
|
||||
if (!syncing) {
|
||||
Thread({ this.scan() }, "Scan Thread").start()
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
fun scan() {
|
||||
update(null)
|
||||
public final void scan() {
|
||||
this.update(null);
|
||||
}
|
||||
|
||||
// MultiThread friendly method
|
||||
fun update(updateListener: UpdateListener?) {
|
||||
@Suppress("NAME_SHADOWING") var updateListener = updateListener
|
||||
if (updateListener == null) updateListener = NO_OP
|
||||
if (!syncing) {
|
||||
public final void update(@Nullable UpdateListener updateListener) {
|
||||
if (updateListener == null) updateListener = NO_OP;
|
||||
if (!this.syncing) {
|
||||
// Do scan
|
||||
synchronized(syncLock) {
|
||||
if (System.currentTimeMillis() < lastSync + 50L) return // Skip sync if it was synced too recently
|
||||
syncing = true
|
||||
synchronized (this.syncLock) {
|
||||
if (System.currentTimeMillis() < this.lastSync + 50L)
|
||||
return; // Skip sync if it was synced too recently
|
||||
this.syncing = true;
|
||||
try {
|
||||
scanInternal(updateListener)
|
||||
this.scanInternal(updateListener);
|
||||
} finally {
|
||||
lastSync = System.currentTimeMillis()
|
||||
syncing = false
|
||||
this.lastSync = System.currentTimeMillis();
|
||||
this.syncing = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Wait for current scan
|
||||
synchronized(syncLock) { Thread.yield() }
|
||||
synchronized (this.syncLock) {
|
||||
Thread.yield();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pause execution until the scan is completed if one is currently running
|
||||
fun afterScan() {
|
||||
if (syncing) synchronized(syncLock) { Thread.yield() }
|
||||
public final void afterScan() {
|
||||
if (this.syncing) synchronized (this.syncLock) { Thread.yield(); }
|
||||
}
|
||||
|
||||
fun runAfterScan(runnable: Runnable) {
|
||||
synchronized(syncLock) { runnable.run() }
|
||||
public final void runAfterScan(Runnable runnable) {
|
||||
synchronized (this.syncLock) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
fun afterUpdate() {
|
||||
if (syncing) synchronized(syncLock) { Thread.yield() }
|
||||
public final void afterUpdate() {
|
||||
if (this.syncing) synchronized (this.syncLock) { Thread.yield(); }
|
||||
}
|
||||
|
||||
fun runAfterUpdate(runnable: Runnable) {
|
||||
synchronized(syncLock) { runnable.run() }
|
||||
public final void runAfterUpdate(Runnable runnable) {
|
||||
synchronized (this.syncLock) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
// This method can't be called twice at the same time.
|
||||
protected abstract fun scanInternal(updateListener: UpdateListener)
|
||||
interface UpdateListener {
|
||||
fun update(value: Double)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val NO_OP: UpdateListener = object : UpdateListener {
|
||||
override fun update(value: Double) {
|
||||
protected abstract void scanInternal(@NonNull UpdateListener updateListener);
|
||||
|
||||
}
|
||||
}
|
||||
public interface UpdateListener {
|
||||
void update(double value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue