Migrate a lot of classes to Kotlin

This commit is contained in:
topjohnwu 2019-06-12 03:29:38 -07:00
parent 00bff4912e
commit 326eee8c83
25 changed files with 649 additions and 788 deletions

View File

@ -1,7 +0,0 @@
package com.topjohnwu.magisk.ui.base
import android.content.Intent
interface ActivityResultListener {
fun onActivityResult(resultCode: Int, data: Intent?)
}

View File

@ -1,11 +0,0 @@
package com.topjohnwu.magisk.ui.base
import android.content.Intent
interface IBaseLeanback {
fun runWithExternalRW(callback: Runnable)
fun runWithPermissions(vararg permissions: String, callback: Runnable)
fun startActivityForResult(intent: Intent, requestCode: Int, listener: ActivityResultListener)
}

View File

@ -1,11 +1,13 @@
package com.topjohnwu.magisk.ui.base package com.topjohnwu.magisk.ui.base
import android.Manifest
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.SparseArrayCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.databinding.ViewDataBinding import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -17,6 +19,7 @@ import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.ncapdevi.fragnav.FragNavController import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.skoumal.teanity.view.TeanityActivity
import com.skoumal.teanity.viewevents.ViewEvent import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.model.events.BackPressEvent import com.topjohnwu.magisk.model.events.BackPressEvent
@ -28,16 +31,20 @@ import com.topjohnwu.magisk.model.navigation.Navigator
import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder import com.topjohnwu.magisk.model.permissions.PermissionRequestBuilder
import com.topjohnwu.magisk.utils.LocaleManager import com.topjohnwu.magisk.utils.LocaleManager
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.utils.set
import timber.log.Timber import timber.log.Timber
import kotlin.reflect.KClass import kotlin.reflect.KClass
typealias RequestCallback = MagiskActivity<*, *>.(Int, Intent?) -> Unit
abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> : abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
MagiskLeanbackActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener, TeanityActivity<ViewModel, Binding>(), FragNavController.RootFragmentListener,
Navigator, FragNavController.TransactionListener { Navigator, FragNavController.TransactionListener {
override val numberOfRootFragments: Int get() = baseFragments.size override val numberOfRootFragments: Int get() = baseFragments.size
override val baseFragments: List<KClass<out Fragment>> = listOf() override val baseFragments: List<KClass<out Fragment>> = listOf()
private val resultCallbacks = SparseArrayCompat<RequestCallback>()
protected open val defaultPosition: Int = 0 protected open val defaultPosition: Int = 0
@ -188,19 +195,23 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
Dexter.withActivity(this) Dexter.withActivity(this)
.withPermissions(*permissions) .withPermissions(*permissions)
.withListener(object : MultiplePermissionsListener { .withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) = override fun onPermissionsChecked(report: MultiplePermissionsReport) {
if (report?.areAllPermissionsGranted() == true) { if (report.areAllPermissionsGranted()) {
request.onSuccess() request.onSuccess()
} else { } else {
request.onFailure() request.onFailure()
} }
}
override fun onPermissionRationaleShouldBeShown( override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?, permissions: MutableList<PermissionRequest>,
token: PermissionToken? token: PermissionToken
) = request.onShowRationale(permissions.orEmpty().map { it.name }) ) = token.continuePermissionRequest()
}) }).check()
.check() }
fun withExternalRW(builder: PermissionRequestBuilder.() -> Unit) {
withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, builder = builder)
} }
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) = private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
@ -210,4 +221,21 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultCallbacks[requestCode]?.apply {
resultCallbacks.remove(requestCode)
invoke(this@MagiskActivity, resultCode, data)
}
}
fun startActivityForResult(
intent: Intent,
requestCode: Int,
listener: RequestCallback
) {
resultCallbacks[requestCode] = listener
startActivityForResult(intent, requestCode)
}
} }

View File

@ -1,64 +0,0 @@
package com.topjohnwu.magisk.ui.base
import android.Manifest
import android.content.Intent
import androidx.collection.SparseArrayCompat
import androidx.databinding.ViewDataBinding
import com.karumi.dexter.Dexter
import com.karumi.dexter.MultiplePermissionsReport
import com.karumi.dexter.PermissionToken
import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import com.skoumal.teanity.view.TeanityActivity
import com.topjohnwu.magisk.Const
abstract class MagiskLeanbackActivity<ViewModel : MagiskViewModel, Binding : ViewDataBinding> :
TeanityActivity<ViewModel, Binding>(), IBaseLeanback {
private val resultListeners = SparseArrayCompat<ActivityResultListener>()
@Deprecated("Permissions will be checked in a different streamlined way")
fun runWithExternalRW(callback: () -> Unit) = runWithExternalRW(Runnable { callback() })
@Deprecated("Permissions will be checked in a different streamlined way")
override fun runWithExternalRW(callback: Runnable) {
runWithPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, callback = callback)
}
@Deprecated("Permissions will be checked in a different streamlined way")
override fun runWithPermissions(vararg permissions: String, callback: Runnable) {
Dexter.withActivity(this)
.withPermissions(*permissions)
.withListener(object : MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
if (report?.areAllPermissionsGranted() == true) {
Const.EXTERNAL_PATH.mkdirs()
callback.run()
}
}
override fun onPermissionRationaleShouldBeShown(
permissions: MutableList<PermissionRequest>?,
token: PermissionToken?
) = Unit
})
.check()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
resultListeners.get(requestCode)?.apply {
resultListeners.remove(requestCode)
onActivityResult(resultCode, data)
}
}
override fun startActivityForResult(
intent: Intent,
requestCode: Int,
listener: ActivityResultListener
) {
resultListeners.put(requestCode, listener)
startActivityForResult(intent, requestCode)
}
}

View File

@ -85,10 +85,12 @@ class ModulesFragment : MagiskFragment<ModuleViewModel, FragmentModulesBinding>(
} }
private fun selectFile() { private fun selectFile() {
magiskActivity.runWithExternalRW { magiskActivity.withExternalRW {
val intent = Intent(Intent.ACTION_GET_CONTENT) onSuccess {
intent.type = "application/zip" val intent = Intent(Intent.ACTION_GET_CONTENT)
startActivityForResult(intent, Const.ID.FETCH_ZIP) intent.type = "application/zip"
startActivityForResult(intent, Const.ID.FETCH_ZIP)
}
} }
} }
} }

View File

@ -74,20 +74,22 @@ class ReposFragment : MagiskFragment<ModuleViewModel, FragmentReposBinding>(),
} }
private fun openChangelog(item: Repo) { private fun openChangelog(item: Repo) {
MarkDownWindow.show(context, null, item.detailUrl) MarkDownWindow.show(requireActivity(), null, item.detailUrl)
} }
private fun installModule(item: Repo) { private fun installModule(item: Repo) {
val context = magiskActivity val context = magiskActivity
fun download(install: Boolean) { fun download(install: Boolean) {
context.runWithExternalRW { context.withExternalRW {
val intent = Intent(activity, ClassMap[DownloadModuleService::class.java]) onSuccess {
.putExtra("repo", item).putExtra("install", install) val intent = Intent(activity, ClassMap[DownloadModuleService::class.java])
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { .putExtra("repo", item).putExtra("install", install)
context.startForegroundService(intent) //hmm, service starts itself in foreground, this seems unnecessary if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { context.startForegroundService(intent)
context.startService(intent) } else {
context.startService(intent)
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.utils package com.topjohnwu.magisk.utils
import androidx.collection.SparseArrayCompat
import androidx.databinding.ObservableList import androidx.databinding.ObservableList
import com.skoumal.teanity.extensions.subscribeK import com.skoumal.teanity.extensions.subscribeK
import com.skoumal.teanity.util.DiffObservableList import com.skoumal.teanity.util.DiffObservableList
@ -76,4 +77,8 @@ fun <T1> ObservableList<T1>.copyNewInputInto(
val addedValues = sender?.slice(positionStart until positionEnd).orEmpty() val addedValues = sender?.slice(positionStart until positionEnd).orEmpty()
target.addAll(addedValues) target.addAll(addedValues)
} }
}) })
operator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {
put(key, value)
}

View File

@ -1,67 +0,0 @@
package com.topjohnwu.magisk.view;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.R;
import com.topjohnwu.net.Networking;
import com.topjohnwu.net.ResponseListener;
import java.io.InputStream;
import java.util.Scanner;
import ru.noties.markwon.Markwon;
import ru.noties.markwon.html.HtmlPlugin;
import ru.noties.markwon.image.ImagesPlugin;
import ru.noties.markwon.image.svg.SvgPlugin;
public class MarkDownWindow {
public static void show(Context activity, String title, String url) {
Networking.get(url).getAsString(new Listener(activity, title));
}
public static void show(Context activity, String title, InputStream is) {
try (Scanner s = new Scanner(is, "UTF-8")) {
s.useDelimiter("\\A");
new Listener(activity, title).onResponse(s.next());
}
}
static class Listener implements ResponseListener<String> {
Context activity;
String title;
Listener(Context a, String t) {
activity = a;
title = t;
}
@Override
public void onResponse(String md) {
Markwon markwon = Markwon.builder(activity)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create(activity))
.usePlugin(SvgPlugin.create(activity.getResources()))
.build();
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
alert.setTitle(title);
View mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null);
TextView tv = mv.findViewById(R.id.md_txt);
try {
markwon.setMarkdown(tv, md);
} catch (ExceptionInInitializerError e) {
//Nothing we can do about this error other than show error message
tv.setText(R.string.download_file_error);
}
alert.setView(mv);
alert.setNegativeButton(R.string.close, (dialog, id) -> dialog.dismiss());
alert.show();
}
}
}

View File

@ -0,0 +1,54 @@
package com.topjohnwu.magisk.view
import android.content.Context
import android.view.LayoutInflater
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import com.topjohnwu.magisk.R
import com.topjohnwu.net.Networking
import com.topjohnwu.net.ResponseListener
import ru.noties.markwon.Markwon
import ru.noties.markwon.html.HtmlPlugin
import ru.noties.markwon.image.ImagesPlugin
import ru.noties.markwon.image.svg.SvgPlugin
import java.io.InputStream
import java.util.*
object MarkDownWindow {
fun show(activity: Context, title: String?, url: String) {
Networking.get(url).getAsString(Listener(activity, title))
}
fun show(activity: Context, title: String?, input: InputStream) {
Scanner(input, "UTF-8").use {
it.useDelimiter("\\A")
Listener(activity, title).onResponse(it.next())
}
}
internal class Listener(var activity: Context, var title: String?) : ResponseListener<String> {
override fun onResponse(md: String) {
val markwon = Markwon.builder(activity)
.usePlugin(HtmlPlugin.create())
.usePlugin(ImagesPlugin.create(activity))
.usePlugin(SvgPlugin.create(activity.resources))
.build()
val alert = AlertDialog.Builder(activity)
alert.setTitle(title)
val mv = LayoutInflater.from(activity).inflate(R.layout.markdown_window, null)
val tv = mv.findViewById<TextView>(R.id.md_txt)
try {
markwon.setMarkdown(tv, md)
} catch (e: ExceptionInInitializerError) {
//Nothing we can do about this error other than show error message
tv.setText(R.string.download_file_error)
}
alert.setView(mv)
alert.setNegativeButton(R.string.close) { dialog, _ -> dialog.dismiss() }
alert.show()
}
}
}

View File

@ -1,85 +0,0 @@
package com.topjohnwu.magisk.view;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import com.topjohnwu.magisk.App;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.net.DownloadProgressListener;
public class ProgressNotification implements DownloadProgressListener {
private NotificationCompat.Builder builder;
private Notification notification;
private long prevTime;
public ProgressNotification(String title) {
builder = Notifications.progress(title);
prevTime = System.currentTimeMillis();
update();
Utils.INSTANCE.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT);
}
@Override
public void onProgress(long bytesDownloaded, long totalBytes) {
long cur = System.currentTimeMillis();
if (cur - prevTime >= 1000) {
prevTime = cur;
int progress = (int) (bytesDownloaded * 100 / totalBytes);
builder.setProgress(100, progress, false);
builder.setContentText(progress + "%");
update();
}
}
public NotificationCompat.Builder getNotificationBuilder() {
return builder;
}
public Notification getNotification() {
return notification;
}
public void update() {
notification = builder.build();
Notifications.mgr.notify(hashCode(), notification);
}
private void lastUpdate() {
notification = builder.build();
Notifications.mgr.cancel(hashCode());
Notifications.mgr.notify(notification.hashCode(), notification);
}
public void dlDone() {
dlDone(PendingIntent.getActivity(App.self, hashCode(),
new Intent(), PendingIntent.FLAG_UPDATE_CURRENT));
}
public void dlDone(PendingIntent intent) {
builder.setProgress(0, 0, false)
.setContentText(App.self.getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(intent)
.setOngoing(false)
.setAutoCancel(true);
lastUpdate();
}
public void dlFail() {
builder.setProgress(0, 0, false)
.setContentText(App.self.getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false);
lastUpdate();
}
public void dismiss() {
Notifications.mgr.cancel(hashCode());
}
}

View File

@ -0,0 +1,73 @@
package com.topjohnwu.magisk.view
import android.app.Notification
import android.app.PendingIntent
import android.content.Intent
import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.topjohnwu.magisk.App
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.net.DownloadProgressListener
class ProgressNotification(title: String) : DownloadProgressListener {
val notificationBuilder: NotificationCompat.Builder = Notifications.progress(title)
lateinit var notification: Notification
private set
private var prevTime: Long = 0
init {
prevTime = System.currentTimeMillis()
update()
Utils.toast(App.self.getString(R.string.downloading_toast, title), Toast.LENGTH_SHORT)
}
override fun onProgress(bytesDownloaded: Long, totalBytes: Long) {
val cur = System.currentTimeMillis()
if (cur - prevTime >= 1000) {
prevTime = cur
val progress = (bytesDownloaded * 100 / totalBytes).toInt()
notificationBuilder.setProgress(100, progress, false)
notificationBuilder.setContentText("$progress%")
update()
}
}
fun update() {
notification = notificationBuilder.build()
Notifications.mgr.notify(hashCode(), notification)
}
private fun lastUpdate() {
Notifications.mgr.cancel(hashCode())
notification = notificationBuilder.build().apply {
Notifications.mgr.notify(hashCode(), this)
}
}
fun dlDone(intent: PendingIntent = PendingIntent.getActivity(App.self, hashCode(),
Intent(), PendingIntent.FLAG_UPDATE_CURRENT)) {
notificationBuilder.setProgress(0, 0, false)
.setContentText(App.self.getString(R.string.download_complete))
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setContentIntent(intent)
.setOngoing(false)
.setAutoCancel(true)
lastUpdate()
}
fun dlFail() {
notificationBuilder.setProgress(0, 0, false)
.setContentText(App.self.getString(R.string.download_file_error))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setOngoing(false)
lastUpdate()
}
fun dismiss() {
Notifications.mgr.cancel(hashCode())
}
}

View File

@ -1,48 +0,0 @@
package com.topjohnwu.magisk.view;
import android.app.Activity;
import android.net.Uri;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.StringRes;
import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.XAndroidKt;
public class SnackbarMaker {
public static Snackbar make(Activity activity, CharSequence text, int duration) {
View view = activity.findViewById(android.R.id.content);
return make(view, text, duration);
}
public static Snackbar make(Activity activity, @StringRes int resId, int duration) {
return make(activity, activity.getString(resId), duration);
}
public static Snackbar make(View view, CharSequence text, int duration) {
Snackbar snack = Snackbar.make(view, text, duration);
setup(snack);
return snack;
}
public static Snackbar make(View view, @StringRes int resId, int duration) {
Snackbar snack = Snackbar.make(view, resId, duration);
setup(snack);
return snack;
}
private static void setup(Snackbar snack) {
TextView text = snack.getView().findViewById(com.google.android.material.R.id.snackbar_text);
text.setMaxLines(Integer.MAX_VALUE);
}
public static void showUri(Activity activity, Uri uri) {
make(activity, activity.getString(R.string.internal_storage,
"/Download/" + XAndroidKt.getFileName(uri)),
Snackbar.LENGTH_LONG)
.setAction(R.string.ok, (v)->{}).show();
}
}

View File

@ -0,0 +1,46 @@
package com.topjohnwu.magisk.view
import android.app.Activity
import android.net.Uri
import android.view.View
import android.widget.TextView
import androidx.annotation.StringRes
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.utils.fileName
object SnackbarMaker {
fun make(activity: Activity, text: CharSequence, duration: Int): Snackbar {
val view = activity.findViewById<View>(android.R.id.content)
return make(view, text, duration)
}
fun make(activity: Activity, @StringRes resId: Int, duration: Int): Snackbar {
return make(activity, activity.getString(resId), duration)
}
fun make(view: View, text: CharSequence, duration: Int): Snackbar {
val snack = Snackbar.make(view, text, duration)
setup(snack)
return snack
}
fun make(view: View, @StringRes resId: Int, duration: Int): Snackbar {
val snack = Snackbar.make(view, resId, duration)
setup(snack)
return snack
}
private fun setup(snack: Snackbar) {
val text = snack.view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
text.maxLines = Integer.MAX_VALUE
}
fun showUri(activity: Activity, uri: Uri) {
make(activity, activity.getString(R.string.internal_storage,
"/Download/" + uri.fileName),
Snackbar.LENGTH_LONG)
.setAction(R.string.ok) { }.show()
}
}

View File

@ -1,137 +0,0 @@
package com.topjohnwu.magisk.view.dialogs;
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.StyleRes;
import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.databinding.AlertDialogBinding;
public class CustomAlertDialog extends AlertDialog.Builder {
private DialogInterface.OnClickListener positiveListener;
private DialogInterface.OnClickListener negativeListener;
private DialogInterface.OnClickListener neutralListener;
protected AlertDialog dialog;
protected AlertDialogBinding binding;
{
binding = AlertDialogBinding.inflate(LayoutInflater.from(getContext()));
super.setView(binding.getRoot());
binding.message.setVisibility(View.GONE);
binding.negative.setVisibility(View.GONE);
binding.positive.setVisibility(View.GONE);
binding.neutral.setVisibility(View.GONE);
binding.buttonPanel.setVisibility(View.GONE);
}
public CustomAlertDialog(@NonNull Context context) {
super(context);
}
public CustomAlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, themeResId);
}
@Override
public CustomAlertDialog setView(int layoutResId) { return this; }
@Override
public CustomAlertDialog setView(View view) { return this; }
@Override
public CustomAlertDialog setMessage(@Nullable CharSequence message) {
binding.message.setVisibility(View.VISIBLE);
binding.message.setText(message);
return this;
}
@Override
public CustomAlertDialog setMessage(@StringRes int messageId) {
return setMessage(getContext().getString(messageId));
}
@Override
public CustomAlertDialog setPositiveButton(CharSequence text, DialogInterface.OnClickListener listener) {
binding.buttonPanel.setVisibility(View.VISIBLE);
binding.positive.setVisibility(View.VISIBLE);
binding.positive.setText(text);
positiveListener = listener;
binding.positive.setOnClickListener(v -> {
if (positiveListener != null) {
positiveListener.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
}
dialog.dismiss();
});
return this;
}
@Override
public CustomAlertDialog setPositiveButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
return setPositiveButton(getContext().getString(textId), listener);
}
@Override
public CustomAlertDialog setNegativeButton(CharSequence text, DialogInterface.OnClickListener listener) {
binding.buttonPanel.setVisibility(View.VISIBLE);
binding.negative.setVisibility(View.VISIBLE);
binding.negative.setText(text);
negativeListener = listener;
binding.negative.setOnClickListener(v -> {
if (negativeListener != null) {
negativeListener.onClick(dialog, DialogInterface.BUTTON_NEGATIVE);
}
dialog.dismiss();
});
return this;
}
@Override
public CustomAlertDialog setNegativeButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
return setNegativeButton(getContext().getString(textId), listener);
}
@Override
public CustomAlertDialog setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) {
binding.buttonPanel.setVisibility(View.VISIBLE);
binding.neutral.setVisibility(View.VISIBLE);
binding.neutral.setText(text);
neutralListener = listener;
binding.neutral.setOnClickListener(v -> {
if (neutralListener != null) {
neutralListener.onClick(dialog, DialogInterface.BUTTON_NEUTRAL);
}
dialog.dismiss();
});
return this;
}
@Override
public CustomAlertDialog setNeutralButton(@StringRes int textId, DialogInterface.OnClickListener listener) {
return setNeutralButton(getContext().getString(textId), listener);
}
@Override
public AlertDialog create() {
dialog = super.create();
return dialog;
}
@Override
public AlertDialog show() {
create();
dialog.show();
return dialog;
}
public void dismiss() {
dialog.dismiss();
}
}

View File

@ -0,0 +1,113 @@
package com.topjohnwu.magisk.view.dialogs
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.View
import androidx.annotation.StringRes
import androidx.annotation.StyleRes
import androidx.appcompat.app.AlertDialog
import com.topjohnwu.magisk.databinding.AlertDialogBinding
open class CustomAlertDialog : AlertDialog.Builder {
private var positiveListener: DialogInterface.OnClickListener? = null
private var negativeListener: DialogInterface.OnClickListener? = null
private var neutralListener: DialogInterface.OnClickListener? = null
protected var dialog: AlertDialog? = null
protected var binding: AlertDialogBinding =
AlertDialogBinding.inflate(LayoutInflater.from(context))
init {
super.setView(binding.root)
binding.message.visibility = View.GONE
binding.negative.visibility = View.GONE
binding.positive.visibility = View.GONE
binding.neutral.visibility = View.GONE
binding.buttonPanel.visibility = View.GONE
}
constructor(context: Context) : super(context)
constructor(context: Context, @StyleRes themeResId: Int) : super(context, themeResId)
override fun setView(layoutResId: Int): CustomAlertDialog {
return this
}
override fun setView(view: View): CustomAlertDialog {
return this
}
override fun setMessage(message: CharSequence?): CustomAlertDialog {
binding.message.visibility = View.VISIBLE
binding.message.text = message
return this
}
override fun setMessage(@StringRes messageId: Int): CustomAlertDialog {
return setMessage(context.getString(messageId))
}
override fun setPositiveButton(text: CharSequence, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
binding.buttonPanel.visibility = View.VISIBLE
binding.positive.visibility = View.VISIBLE
binding.positive.text = text
positiveListener = listener
binding.positive.setOnClickListener {
positiveListener?.onClick(dialog, DialogInterface.BUTTON_POSITIVE)
dialog?.dismiss()
}
return this
}
override fun setPositiveButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
return setPositiveButton(context.getString(textId), listener)
}
override fun setNegativeButton(text: CharSequence, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
binding.buttonPanel.visibility = View.VISIBLE
binding.negative.visibility = View.VISIBLE
binding.negative.text = text
negativeListener = listener
binding.negative.setOnClickListener {
negativeListener?.onClick(dialog, DialogInterface.BUTTON_NEGATIVE)
dialog?.dismiss()
}
return this
}
override fun setNegativeButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
return setNegativeButton(context.getString(textId), listener)
}
override fun setNeutralButton(text: CharSequence, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
binding.buttonPanel.visibility = View.VISIBLE
binding.neutral.visibility = View.VISIBLE
binding.neutral.text = text
neutralListener = listener
binding.neutral.setOnClickListener {
neutralListener?.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
dialog?.dismiss()
}
return this
}
override fun setNeutralButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener?): CustomAlertDialog {
return setNeutralButton(context.getString(textId), listener)
}
override fun create(): AlertDialog {
return super.create().apply { dialog = this }
}
override fun show(): AlertDialog {
return create().apply { show() }
}
fun dismiss() {
dialog?.dismiss()
}
}

View File

@ -1,108 +0,0 @@
package com.topjohnwu.magisk.view.dialogs;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.view.Gravity;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.FingerprintHelper;
import com.topjohnwu.magisk.utils.Utils;
@TargetApi(Build.VERSION_CODES.M)
public class FingerprintAuthDialog extends CustomAlertDialog {
private final Runnable callback;
@Nullable
private Runnable failureCallback;
private DialogFingerprintHelper helper;
public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess) {
super(activity);
callback = onSuccess;
Drawable fingerprint = activity.getResources().getDrawable(R.drawable.ic_fingerprint);
fingerprint.setBounds(0, 0, Utils.INSTANCE.dpInPx(50), Utils.INSTANCE.dpInPx(50));
Resources.Theme theme = activity.getTheme();
TypedArray ta = theme.obtainStyledAttributes(new int[] {R.attr.imageColorTint});
fingerprint.setTint(ta.getColor(0, Color.GRAY));
ta.recycle();
binding.message.setCompoundDrawables(null, null, null, fingerprint);
binding.message.setCompoundDrawablePadding(Utils.INSTANCE.dpInPx(20));
binding.message.setGravity(Gravity.CENTER);
setMessage(R.string.auth_fingerprint);
setNegativeButton(R.string.close, (d, w) -> {
helper.cancel();
if (failureCallback != null) {
failureCallback.run();
}
});
setOnCancelListener(d -> {
helper.cancel();
if (failureCallback != null) {
failureCallback.run();
}
});
try {
helper = new DialogFingerprintHelper();
} catch (Exception ignored) {
ignored.printStackTrace();
}
}
public FingerprintAuthDialog(@NonNull Activity activity, @NonNull Runnable onSuccess, @NonNull Runnable onFailure) {
this(activity, onSuccess);
failureCallback = onFailure;
}
@Override
public AlertDialog show() {
create();
if (helper == null) {
dialog.dismiss();
Utils.INSTANCE.toast(R.string.auth_fail, Toast.LENGTH_SHORT);
} else {
helper.authenticate();
dialog.show();
}
return dialog;
}
class DialogFingerprintHelper extends FingerprintHelper {
DialogFingerprintHelper() throws Exception {}
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
binding.message.setTextColor(Color.RED);
binding.message.setText(errString);
}
@Override
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
binding.message.setTextColor(Color.RED);
binding.message.setText(helpString);
}
@Override
public void onAuthenticationFailed() {
binding.message.setTextColor(Color.RED);
binding.message.setText(R.string.auth_fail);
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
dismiss();
callback.run();
}
}
}

View File

@ -0,0 +1,88 @@
package com.topjohnwu.magisk.view.dialogs
import android.annotation.TargetApi
import android.app.Activity
import android.graphics.Color
import android.hardware.fingerprint.FingerprintManager
import android.os.Build
import android.view.Gravity
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.Utils
@TargetApi(Build.VERSION_CODES.M)
class FingerprintAuthDialog(activity: Activity, private val callback: () -> Unit)
: CustomAlertDialog(activity) {
private var failureCallback: (() -> Unit)? = null
private var helper: DialogFingerprintHelper? = null
init {
val fingerprint = ContextCompat.getDrawable(activity, R.drawable.ic_fingerprint)
fingerprint?.setBounds(0, 0, Utils.dpInPx(50), Utils.dpInPx(50))
val theme = activity.theme
val ta = theme.obtainStyledAttributes(intArrayOf(R.attr.imageColorTint))
fingerprint?.setTint(ta.getColor(0, Color.GRAY))
ta.recycle()
binding.message.setCompoundDrawables(null, null, null, fingerprint)
binding.message.compoundDrawablePadding = Utils.dpInPx(20)
binding.message.gravity = Gravity.CENTER
setMessage(R.string.auth_fingerprint)
setNegativeButton(R.string.close) { _, _ ->
helper?.cancel()
failureCallback?.invoke()
}
setOnCancelListener {
helper?.cancel()
failureCallback?.invoke()
}
runCatching {
helper = DialogFingerprintHelper()
}
}
constructor(activity: Activity, onSuccess: () -> Unit, onFailure: () -> Unit)
: this(activity, onSuccess) {
failureCallback = onFailure
}
override fun show(): AlertDialog {
return create().apply {
if (helper == null) {
dismiss()
Utils.toast(R.string.auth_fail, Toast.LENGTH_SHORT)
} else {
helper?.authenticate()
show()
}
}
}
internal inner class DialogFingerprintHelper @Throws(Exception::class)
constructor() : FingerprintHelper() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
binding.message.setTextColor(Color.RED)
binding.message.text = errString
}
override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence) {
binding.message.setTextColor(Color.RED)
binding.message.text = helpString
}
override fun onAuthenticationFailed() {
binding.message.setTextColor(Color.RED)
binding.message.setText(R.string.auth_fail)
}
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult) {
dismiss()
callback()
}
}
}

View File

@ -1,100 +0,0 @@
package com.topjohnwu.magisk.view.dialogs;
import android.app.Activity;
import android.content.Intent;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.snackbar.Snackbar;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.IBaseLeanback;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.view.ProgressNotification;
import com.topjohnwu.magisk.view.SnackbarMaker;
import com.topjohnwu.net.Networking;
import java.io.File;
import java.util.List;
class InstallMethodDialog extends AlertDialog.Builder {
<Ctxt extends Activity & IBaseLeanback> InstallMethodDialog(Ctxt activity, List<String> options) {
super(activity);
setTitle(R.string.select_method);
setItems(options.toArray(new String[0]), (dialog, idx) -> {
Intent intent;
switch (idx) {
case 1:
patchBoot(activity);
break;
case 0:
downloadOnly(activity);
break;
case 2:
intent = new Intent(activity, ClassMap.get(FlashActivity.class))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK);
activity.startActivity(intent);
break;
case 3:
installInactiveSlot(activity);
break;
default:
}
});
}
private <Ctxt extends Activity & IBaseLeanback> void patchBoot(Ctxt activity) {
activity.runWithExternalRW(() -> {
Utils.INSTANCE.toast(R.string.patch_file_msg, Toast.LENGTH_LONG);
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
.setType("*/*")
.addCategory(Intent.CATEGORY_OPENABLE);
activity.startActivityForResult(intent, Const.ID.SELECT_BOOT,
(resultCode, data) -> {
if (resultCode == Activity.RESULT_OK && data != null) {
Intent i = new Intent(activity, ClassMap.get(FlashActivity.class))
.setData(data.getData())
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_FILE);
activity.startActivity(i);
}
});
});
}
private <Ctxt extends Activity & IBaseLeanback> void downloadOnly(Ctxt activity) {
activity.runWithExternalRW(() -> {
String filename = Utils.INSTANCE.fmt("Magisk-v%s(%d).zip",
Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode);
File zip = new File(Const.EXTERNAL_PATH, filename);
ProgressNotification progress = new ProgressNotification(filename);
Networking.get(Info.magiskLink)
.setDownloadProgressListener(progress)
.setErrorHandler((conn, e) -> progress.dlFail())
.getAsFile(zip, f -> {
progress.dlDone();
SnackbarMaker.make(activity,
activity.getString(R.string.internal_storage, "/Download/" + filename),
Snackbar.LENGTH_LONG).show();
});
});
}
private <Ctxt extends Activity & IBaseLeanback> void installInactiveSlot(Ctxt activity) {
new CustomAlertDialog(activity)
.setTitle(R.string.warning)
.setMessage(R.string.install_inactive_slot_msg)
.setCancelable(true)
.setPositiveButton(R.string.yes, (d, i) -> {
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT);
activity.startActivity(intent);
})
.setNegativeButton(R.string.no_thanks, null)
.show();
}
}

View File

@ -0,0 +1,90 @@
package com.topjohnwu.magisk.view.dialogs
import android.app.Activity
import android.content.Intent
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.google.android.material.snackbar.Snackbar
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.ProgressNotification
import com.topjohnwu.magisk.view.SnackbarMaker
import com.topjohnwu.net.Networking
import java.io.File
internal class InstallMethodDialog(activity: MagiskActivity<*, *>, options: List<String>) : AlertDialog.Builder(activity) {
init {
setTitle(R.string.select_method)
setItems(options.toTypedArray()) { _, idx ->
when (idx) {
0 -> downloadOnly(activity)
1 -> patchBoot(activity)
2 -> {
val intent = Intent(activity, ClassMap[FlashActivity::class.java])
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_MAGISK)
activity.startActivity(intent)
}
3 -> installInactiveSlot(activity)
}
}
}
private fun patchBoot(activity: MagiskActivity<*, *>) {
activity.withExternalRW {
onSuccess {
Utils.toast(R.string.patch_file_msg, Toast.LENGTH_LONG)
val intent = Intent(Intent.ACTION_GET_CONTENT)
.setType("*/*")
.addCategory(Intent.CATEGORY_OPENABLE)
activity.startActivityForResult(intent, Const.ID.SELECT_BOOT) { resultCode, data ->
if (resultCode == Activity.RESULT_OK && data != null) {
val i = Intent(this, ClassMap[FlashActivity::class.java])
.setData(data.data)
.putExtra(Const.Key.FLASH_ACTION, Const.Value.PATCH_FILE)
startActivity(i)
}
}
}
}
}
private fun downloadOnly(activity: MagiskActivity<*, *>) {
activity.withExternalRW {
onSuccess {
val filename = "Magisk-v${Info.remoteMagiskVersionString}" +
"(${Info.remoteMagiskVersionCode}).zip"
val zip = File(Const.EXTERNAL_PATH, filename)
val progress = ProgressNotification(filename)
Networking.get(Info.magiskLink)
.setDownloadProgressListener(progress)
.setErrorHandler { _, _ -> progress.dlFail() }
.getAsFile(zip) {
progress.dlDone()
SnackbarMaker.make(activity,
activity.getString(R.string.internal_storage, "/Download/$filename"),
Snackbar.LENGTH_LONG).show()
}
}
}
}
private fun installInactiveSlot(activity: MagiskActivity<*, *>) {
CustomAlertDialog(activity)
.setTitle(R.string.warning)
.setMessage(R.string.install_inactive_slot_msg)
.setCancelable(true)
.setPositiveButton(R.string.yes) { _, _ ->
val intent = Intent(activity, ClassMap[FlashActivity::class.java])
.putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_INACTIVE_SLOT)
activity.startActivity(intent)
}
.setNegativeButton(R.string.no_thanks, null)
.show()
}
}

View File

@ -1,50 +0,0 @@
package com.topjohnwu.magisk.view.dialogs;
import android.app.Activity;
import android.net.Uri;
import android.text.TextUtils;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.base.IBaseLeanback;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.view.MarkDownWindow;
import com.topjohnwu.superuser.Shell;
import com.topjohnwu.superuser.ShellUtils;
import java.util.ArrayList;
import java.util.List;
public class MagiskInstallDialog extends CustomAlertDialog {
public <Ctxt extends Activity & IBaseLeanback> MagiskInstallDialog(Ctxt a) {
super(a);
String filename = Utils.INSTANCE.fmt("Magisk-v%s(%d).zip",
Info.remoteMagiskVersionString, Info.remoteMagiskVersionCode);
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)));
setMessage(a.getString(R.string.repo_install_msg, filename));
setCancelable(true);
setPositiveButton(R.string.install, (d, i) -> {
List<String> options = new ArrayList<>();
options.add(a.getString(R.string.download_zip_only));
options.add(a.getString(R.string.select_patch_file));
if (Shell.rootAccess()) {
options.add(a.getString(R.string.direct_install));
String s = ShellUtils.fastCmd("grep_prop ro.build.ab_update");
if (!s.isEmpty() && Boolean.parseBoolean(s)) {
options.add(a.getString(R.string.install_inactive_slot));
}
}
new InstallMethodDialog(a, options).show();
});
if (!TextUtils.isEmpty(Info.magiskNoteLink)) {
setNeutralButton(R.string.release_notes, (d, i) -> {
if (Info.magiskNoteLink.contains("forum.xda-developers")) {
// Open forum links in browser
Utils.INSTANCE.openLink(a, Uri.parse(Info.magiskNoteLink));
} else {
MarkDownWindow.show(a, null, Info.magiskNoteLink);
}
});
}
}
}

View File

@ -0,0 +1,45 @@
package com.topjohnwu.magisk.view.dialogs
import android.net.Uri
import android.text.TextUtils
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.base.MagiskActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.MarkDownWindow
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.util.*
class MagiskInstallDialog(a: MagiskActivity<*, *>) : CustomAlertDialog(a) {
init {
val filename = "Magisk v${Info.remoteMagiskVersionString}" +
"(${Info.remoteMagiskVersionCode})"
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.magisk)))
setMessage(a.getString(R.string.repo_install_msg, filename))
setCancelable(true)
setPositiveButton(R.string.install) { _, _ ->
val options = ArrayList<String>()
options.add(a.getString(R.string.download_zip_only))
options.add(a.getString(R.string.select_patch_file))
if (Shell.rootAccess()) {
options.add(a.getString(R.string.direct_install))
val s = ShellUtils.fastCmd("grep_prop ro.build.ab_update")
if (s.isNotEmpty() && s.toBoolean()) {
options.add(a.getString(R.string.install_inactive_slot))
}
}
InstallMethodDialog(a, options).show()
}
if (!TextUtils.isEmpty(Info.magiskNoteLink)) {
setNeutralButton(R.string.release_notes) { _, _ ->
if (Info.magiskNoteLink.contains("forum.xda-developers")) {
// Open forum links in browser
Utils.openLink(a, Uri.parse(Info.magiskNoteLink))
} else {
MarkDownWindow.show(a, null, Info.magiskNoteLink)
}
}
}
}
}

View File

@ -1,28 +0,0 @@
package com.topjohnwu.magisk.view.dialogs;
import android.app.Activity;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.utils.DownloadApp;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.view.MarkDownWindow;
public class ManagerInstallDialog extends CustomAlertDialog {
public ManagerInstallDialog(@NonNull Activity a) {
super(a);
String name = Utils.INSTANCE.fmt("MagiskManager v%s(%d)",
Info.remoteManagerVersionString, Info.remoteManagerVersionCode);
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)));
setMessage(a.getString(R.string.repo_install_msg, name));
setCancelable(true);
setPositiveButton(R.string.install, (d, i) -> DownloadApp.upgrade(name));
if (!TextUtils.isEmpty(Info.managerNoteLink)) {
setNeutralButton(R.string.app_changelog, (d, i) -> MarkDownWindow.show(a, null, Info.managerNoteLink));
}
}
}

View File

@ -0,0 +1,23 @@
package com.topjohnwu.magisk.view.dialogs
import android.app.Activity
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.utils.DownloadApp
import com.topjohnwu.magisk.view.MarkDownWindow
class ManagerInstallDialog(a: Activity) : CustomAlertDialog(a) {
init {
val name = "MagiskManager v${Info.remoteManagerVersionString}" +
"(${Info.remoteManagerVersionCode})"
setTitle(a.getString(R.string.repo_install_title, a.getString(R.string.app_name)))
setMessage(a.getString(R.string.repo_install_msg, name))
setCancelable(true)
setPositiveButton(R.string.install) { _, _ -> DownloadApp.upgrade(name) }
if (Info.managerNoteLink.isNotEmpty()) {
setNeutralButton(R.string.app_changelog) { _, _ ->
MarkDownWindow.show(a, null, Info.managerNoteLink) }
}
}
}

View File

@ -1,61 +0,0 @@
package com.topjohnwu.magisk.view.dialogs;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.topjohnwu.magisk.ClassMap;
import com.topjohnwu.magisk.Const;
import com.topjohnwu.magisk.Info;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.ui.flash.FlashActivity;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.view.ProgressNotification;
import com.topjohnwu.net.Networking;
import com.topjohnwu.superuser.Shell;
import java.io.File;
public class UninstallDialog extends CustomAlertDialog {
public UninstallDialog(@NonNull Activity activity) {
super(activity);
setTitle(R.string.uninstall_magisk_title);
setMessage(R.string.uninstall_magisk_msg);
setNeutralButton(R.string.restore_img, (d, i) -> {
ProgressDialog dialog = ProgressDialog.show(activity,
activity.getString(R.string.restore_img),
activity.getString(R.string.restore_img_msg));
Shell.su("restore_imgs").submit(result -> {
dialog.cancel();
if (result.isSuccess()) {
Utils.INSTANCE.toast(R.string.restore_done, Toast.LENGTH_SHORT);
} else {
Utils.INSTANCE.toast(R.string.restore_fail, Toast.LENGTH_LONG);
}
});
});
if (!TextUtils.isEmpty(Info.uninstallerLink)) {
setPositiveButton(R.string.complete_uninstall, (d, i) -> {
File zip = new File(activity.getFilesDir(), "uninstaller.zip");
ProgressNotification progress = new ProgressNotification(zip.getName());
Networking.get(Info.uninstallerLink)
.setDownloadProgressListener(progress)
.setErrorHandler(((conn, e) -> progress.dlFail()))
.getAsFile(zip, f -> {
progress.dismiss();
Intent intent = new Intent(activity, ClassMap.get(FlashActivity.class))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.fromFile(f))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL);
activity.startActivity(intent);
});
});
}
}
}

View File

@ -0,0 +1,58 @@
package com.topjohnwu.magisk.view.dialogs
import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import android.widget.Toast
import com.topjohnwu.magisk.ClassMap
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.Info
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.ui.flash.FlashActivity
import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.magisk.view.ProgressNotification
import com.topjohnwu.net.Networking
import com.topjohnwu.superuser.Shell
import java.io.File
class UninstallDialog(activity: Activity) : CustomAlertDialog(activity) {
init {
setTitle(R.string.uninstall_magisk_title)
setMessage(R.string.uninstall_magisk_msg)
setNeutralButton(R.string.restore_img) { _, _ ->
val dialog = ProgressDialog.show(activity,
activity.getString(R.string.restore_img),
activity.getString(R.string.restore_img_msg))
Shell.su("restore_imgs").submit { result ->
dialog.cancel()
if (result.isSuccess) {
Utils.toast(R.string.restore_done, Toast.LENGTH_SHORT)
} else {
Utils.toast(R.string.restore_fail, Toast.LENGTH_LONG)
}
}
}
if (!TextUtils.isEmpty(Info.uninstallerLink)) {
setPositiveButton(R.string.complete_uninstall) { d, i ->
val zip = File(activity.filesDir, "uninstaller.zip")
val progress = ProgressNotification(zip.name)
Networking.get(Info.uninstallerLink)
.setDownloadProgressListener(progress)
.setErrorHandler { _, _ -> progress.dlFail() }
.getAsFile(zip) { f ->
progress.dismiss()
val intent = Intent(activity, ClassMap[FlashActivity::class.java])
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(Uri.fromFile(f))
.putExtra(Const.Key.FLASH_ACTION, Const.Value.UNINSTALL)
activity.startActivity(intent)
}
}
}
}
}