diff --git a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt index e092ee0d8..5db8c8ac5 100644 --- a/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt +++ b/app/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt @@ -1,6 +1,8 @@ package com.topjohnwu.magisk.arch -import android.Manifest.permission.* +import android.Manifest.permission.POST_NOTIFICATIONS +import android.Manifest.permission.REQUEST_INSTALL_PACKAGES +import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.annotation.SuppressLint import android.os.Bundle import androidx.databinding.PropertyChangeRegistry diff --git a/app/src/main/java/com/topjohnwu/magisk/core/JobService.kt b/app/src/main/java/com/topjohnwu/magisk/core/JobService.kt index bfe770f10..18ac5dfa7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/JobService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/JobService.kt @@ -11,7 +11,7 @@ import androidx.core.content.getSystemService import com.topjohnwu.magisk.BuildConfig import com.topjohnwu.magisk.core.base.BaseJobService import com.topjohnwu.magisk.core.di.ServiceLocator -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.view.Notifications import kotlinx.coroutines.Dispatchers @@ -22,12 +22,12 @@ import java.util.concurrent.TimeUnit class JobService : BaseJobService() { private var mSession: Session? = null - private var mDm: DownloadManager? = null + private var mEngine: DownloadEngine? = null @TargetApi(value = 34) inner class Session( var params: JobParameters - ) : DownloadManager.Session { + ) : DownloadEngine.Session { override val context get() = this@JobService @@ -55,7 +55,7 @@ class JobService : BaseJobService() { private fun downloadFile(params: JobParameters): Boolean { params.transientExtras.classLoader = Subject::class.java.classLoader val subject = params.transientExtras - .getParcelable(DownloadManager.SUBJECT_KEY, Subject::class.java) ?: + .getParcelable(DownloadEngine.SUBJECT_KEY, Subject::class.java) ?: return false val session = mSession?.also { @@ -64,13 +64,13 @@ class JobService : BaseJobService() { Session(params).also { mSession = it } } - val dm = mDm?.also { + val engine = mEngine?.also { it.reattach() } ?: run { - DownloadManager(session).also { mDm = it } + DownloadEngine(session).also { mEngine = it } } - dm.download(subject) + engine.download(subject) return true } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Receiver.kt b/app/src/main/java/com/topjohnwu/magisk/core/Receiver.kt index c6acff23a..635e384a1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Receiver.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Receiver.kt @@ -6,7 +6,7 @@ import android.content.Intent import androidx.core.content.IntentCompat import com.topjohnwu.magisk.core.base.BaseReceiver import com.topjohnwu.magisk.core.di.ServiceLocator -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Shortcuts @@ -38,10 +38,10 @@ open class Receiver : BaseReceiver() { } when (intent.action ?: return) { - DownloadManager.ACTION -> { + DownloadEngine.ACTION -> { IntentCompat.getParcelableExtra( - intent, DownloadManager.SUBJECT_KEY, Subject::class.java)?.let { - DownloadManager.start(context, it) + intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)?.let { + DownloadEngine.start(context, it) } } Intent.ACTION_PACKAGE_REPLACED -> { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Service.kt b/app/src/main/java/com/topjohnwu/magisk/core/Service.kt index 5398c8497..0f3324cac 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Service.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Service.kt @@ -6,24 +6,24 @@ import android.os.Build import androidx.core.app.ServiceCompat import androidx.core.content.IntentCompat import com.topjohnwu.magisk.core.base.BaseService -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.Subject -class Service : BaseService(), DownloadManager.Session { +class Service : BaseService(), DownloadEngine.Session { - private lateinit var dm: DownloadManager + private lateinit var mEngine: DownloadEngine override val context get() = this override fun onCreate() { super.onCreate() - dm = DownloadManager(this) + mEngine = DownloadEngine(this) } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { - if (intent.action == DownloadManager.ACTION) { + if (intent.action == DownloadEngine.ACTION) { IntentCompat - .getParcelableExtra(intent, DownloadManager.SUBJECT_KEY, Subject::class.java) - ?.let { dm.download(it) } + .getParcelableExtra(intent, DownloadEngine.SUBJECT_KEY, Subject::class.java) + ?.let { mEngine.download(it) } } return START_NOT_STICKY } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/data/NetworkServices.kt b/app/src/main/java/com/topjohnwu/magisk/core/data/NetworkServices.kt index 4fa17b1e8..be9913123 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/data/NetworkServices.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/data/NetworkServices.kt @@ -4,7 +4,11 @@ import com.topjohnwu.magisk.core.model.BranchInfo import com.topjohnwu.magisk.core.model.ModuleJson import com.topjohnwu.magisk.core.model.UpdateInfo import okhttp3.ResponseBody -import retrofit2.http.* +import retrofit2.http.GET +import retrofit2.http.Headers +import retrofit2.http.Path +import retrofit2.http.Streaming +import retrofit2.http.Url private const val BRANCH = "branch" private const val REPO = "repo" diff --git a/app/src/main/java/com/topjohnwu/magisk/core/data/SuLogDao.kt b/app/src/main/java/com/topjohnwu/magisk/core/data/SuLogDao.kt index 8b1fe7ddb..da99afbf0 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/data/SuLogDao.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/data/SuLogDao.kt @@ -1,12 +1,16 @@ package com.topjohnwu.magisk.core.data -import androidx.room.* +import androidx.room.Dao +import androidx.room.Database +import androidx.room.Insert +import androidx.room.Query +import androidx.room.RoomDatabase import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.topjohnwu.magisk.core.model.su.SuLog import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.util.* +import java.util.Calendar @Database(version = 2, entities = [SuLog::class], exportSchema = false) abstract class SuLogDatabase : RoomDatabase() { diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadManager.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt similarity index 93% rename from app/src/main/java/com/topjohnwu/magisk/core/download/DownloadManager.kt rename to app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt index 157e052c5..cf6802bd7 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadManager.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt @@ -55,10 +55,27 @@ import java.util.zip.ZipFile import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream -class DownloadManager( +/** + * This class drives the execution of file downloads and notification management. + * + * Each download engine instance has to be paired with a "session" that is managed by the operating + * system. A session is an Android component that allows executing long lasting operations and + * have its state tied to a notification to show progress. + * + * A session can only have one single notification representing its state, and the operating system + * also uses the notification to manage the lifecycle of a session. One goal of this class is + * to support concurrent download tasks using only one single session, so internally it manages + * all active tasks and notifications and properly re-assign notifications to be attached to + * the session to make sure all download operations can be completed without the operating system + * killing the session. + * + * For API 23 - 33, we use a foreground service as a session. + * For API 34 and higher, we use user-initiated job services as a session. + */ +class DownloadEngine( private val session: Session ) { - + interface Session { val context: Context @@ -96,7 +113,6 @@ class DownloadManager( .putExtra(SUBJECT_KEY, subject) } - @SuppressLint("InlinedApi") fun getPendingIntent(context: Context, subject: Subject): PendingIntent { val flag = PendingIntent.FLAG_IMMUTABLE or @@ -167,7 +183,7 @@ class DownloadManager( notifyFail(subject) } - synchronized(this@DownloadManager) { + synchronized(this@DownloadEngine) { if (notifications.isEmpty) session.stop() } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt index 83a4a61ae..aac1c4f36 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/Locales.kt @@ -12,7 +12,7 @@ import com.topjohnwu.magisk.core.createNewResources import com.topjohnwu.magisk.core.di.AppContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.util.* +import java.util.Locale var currentLocale: Locale = Locale.getDefault() diff --git a/app/src/main/java/com/topjohnwu/magisk/databinding/DataBindingAdapters.kt b/app/src/main/java/com/topjohnwu/magisk/databinding/DataBindingAdapters.kt index a162a2b76..6cb54a722 100644 --- a/app/src/main/java/com/topjohnwu/magisk/databinding/DataBindingAdapters.kt +++ b/app/src/main/java/com/topjohnwu/magisk/databinding/DataBindingAdapters.kt @@ -8,7 +8,12 @@ import android.text.Spanned import android.util.TypedValue import android.view.View import android.view.ViewGroup -import android.widget.* +import android.widget.ArrayAdapter +import android.widget.Button +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.Spinner +import android.widget.TextView import androidx.annotation.DrawableRes import androidx.appcompat.widget.Toolbar import androidx.cardview.widget.CardView @@ -20,7 +25,11 @@ import androidx.databinding.BindingAdapter import androidx.databinding.InverseBindingAdapter import androidx.databinding.InverseBindingListener import androidx.interpolator.view.animation.FastOutSlowInInterpolator -import androidx.recyclerview.widget.* +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.StaggeredGridLayoutManager import com.google.android.material.button.MaterialButton import com.google.android.material.card.MaterialCardView import com.google.android.material.chip.Chip diff --git a/app/src/main/java/com/topjohnwu/magisk/databinding/MergeObservableList.kt b/app/src/main/java/com/topjohnwu/magisk/databinding/MergeObservableList.kt index 75543be9d..4d06f1d7d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/databinding/MergeObservableList.kt +++ b/app/src/main/java/com/topjohnwu/magisk/databinding/MergeObservableList.kt @@ -3,7 +3,7 @@ package com.topjohnwu.magisk.databinding import androidx.databinding.ListChangeRegistry import androidx.databinding.ObservableList import androidx.databinding.ObservableList.OnListChangedCallback -import java.util.* +import java.util.AbstractList @Suppress("UNCHECKED_CAST") class MergeObservableList : AbstractList(), ObservableList { diff --git a/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt b/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt index 2ac866bc5..a02c040af 100644 --- a/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt @@ -4,7 +4,7 @@ import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.di.AppContext import com.topjohnwu.magisk.core.di.ServiceLocator -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.view.MagiskDialog import java.io.File @@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() { setCancelable(true) setButton(MagiskDialog.ButtonType.POSITIVE) { text = R.string.install - onClick { DownloadManager.startWithActivity(activity, Subject.App()) } + onClick { DownloadEngine.startWithActivity(activity, Subject.App()) } } setButton(MagiskDialog.ButtonType.NEGATIVE) { text = android.R.string.cancel diff --git a/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt b/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt index a5dba2949..65e52b757 100644 --- a/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt +++ b/app/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt @@ -3,7 +3,7 @@ package com.topjohnwu.magisk.dialog import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.download.Action -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.model.module.OnlineModule import com.topjohnwu.magisk.view.MagiskDialog @@ -24,7 +24,7 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog fun download(install: Boolean) { val action = if (install) Action.Flash else Action.Download val subject = Subject.Module(item, action) - DownloadManager.startWithActivity(activity, subject) + DownloadEngine.startWithActivity(activity, subject) } val title = context.getString(R.string.repo_install_title, diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt b/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt index 59a2ba7b7..aec75c5e1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt @@ -4,7 +4,12 @@ import android.annotation.SuppressLint import android.content.pm.ApplicationInfo import android.content.pm.ComponentInfo import android.content.pm.PackageManager -import android.content.pm.PackageManager.* +import android.content.pm.PackageManager.GET_ACTIVITIES +import android.content.pm.PackageManager.GET_PROVIDERS +import android.content.pm.PackageManager.GET_RECEIVERS +import android.content.pm.PackageManager.GET_SERVICES +import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS +import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.content.pm.ServiceInfo import android.graphics.drawable.Drawable import android.os.Build @@ -12,7 +17,7 @@ import android.os.Build.VERSION.SDK_INT import androidx.core.os.ProcessCompat import com.topjohnwu.magisk.core.ktx.getLabel import com.topjohnwu.magisk.core.utils.currentLocale -import java.util.* +import java.util.TreeSet class CmdlineListItem(line: String) { val packageName: String diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt index a3ed40ef7..48dc0e481 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt @@ -5,7 +5,11 @@ import android.content.Context import android.content.pm.ActivityInfo import android.net.Uri import android.os.Bundle -import android.view.* +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View import androidx.core.view.MenuProvider import androidx.core.view.isVisible import androidx.navigation.NavDeepLinkBuilder diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt index 2a7bcc6df..0ea16ea2d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt @@ -14,7 +14,7 @@ import com.topjohnwu.magisk.R import com.topjohnwu.magisk.arch.BaseFragment import com.topjohnwu.magisk.arch.viewModel import com.topjohnwu.magisk.core.Info -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding class HomeFragment : BaseFragment(), MenuProvider { @@ -25,7 +25,7 @@ class HomeFragment : BaseFragment(), MenuProvider { override fun onStart() { super.onStart() activity?.setTitle(R.string.section_home) - DownloadManager.observeProgress(this, viewModel::onProgressUpdate) + DownloadEngine.observeProgress(this, viewModel::onProgressUpdate) } private fun checkTitle(text: TextView, icon: ImageView) { diff --git a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt index c3fcd7c13..dad376991 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt @@ -11,7 +11,7 @@ import androidx.core.content.getSystemService import androidx.core.graphics.drawable.toIcon import com.topjohnwu.magisk.R import com.topjohnwu.magisk.core.di.AppContext -import com.topjohnwu.magisk.core.download.DownloadManager +import com.topjohnwu.magisk.core.download.DownloadEngine import com.topjohnwu.magisk.core.download.Subject import com.topjohnwu.magisk.core.ktx.getBitmap import com.topjohnwu.magisk.core.ktx.selfLaunchIntent @@ -67,7 +67,7 @@ object Notifications { fun updateAvailable() { AppContext.apply { - val intent = DownloadManager.getPendingIntent(this, Subject.App()) + val intent = DownloadEngine.getPendingIntent(this, Subject.App()) val bitmap = getBitmap(R.drawable.ic_magisk_outline) val builder = if (SDK_INT >= Build.VERSION_CODES.O) { Notification.Builder(this, UPDATE_CHANNEL)