mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 07:57:39 +00:00
Use user-initiated jobs for download tasks on API 34+
This commit is contained in:
parent
c5d34670c4
commit
38ad871e33
@ -10,6 +10,7 @@
|
|||||||
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
|
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
|
||||||
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="29" />
|
android:maxSdkVersion="29" />
|
||||||
|
@ -35,7 +35,8 @@ object Const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object ID {
|
object ID {
|
||||||
const val JOB_SERVICE_ID = 7
|
const val DOWNLOAD_JOB_ID = 6
|
||||||
|
const val CHECK_UPDATE_JOB_ID = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
object Url {
|
object Url {
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.core
|
package com.topjohnwu.magisk.core
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Notification
|
||||||
import android.app.job.JobInfo
|
import android.app.job.JobInfo
|
||||||
import android.app.job.JobParameters
|
import android.app.job.JobParameters
|
||||||
import android.app.job.JobScheduler
|
import android.app.job.JobScheduler
|
||||||
@ -8,38 +11,79 @@ import androidx.core.content.getSystemService
|
|||||||
import com.topjohnwu.magisk.BuildConfig
|
import com.topjohnwu.magisk.BuildConfig
|
||||||
import com.topjohnwu.magisk.core.base.BaseJobService
|
import com.topjohnwu.magisk.core.base.BaseJobService
|
||||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||||
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class JobService : BaseJobService() {
|
class JobService : BaseJobService() {
|
||||||
|
|
||||||
private val job = Job()
|
private var mSession: Session? = null
|
||||||
private val svc get() = ServiceLocator.networkService
|
private var mDm: DownloadManager? = null
|
||||||
|
|
||||||
override fun onStartJob(params: JobParameters): Boolean {
|
@TargetApi(value = 34)
|
||||||
val coroutineScope = CoroutineScope(Dispatchers.IO + job)
|
inner class Session(
|
||||||
coroutineScope.launch {
|
var params: JobParameters
|
||||||
doWork()
|
) : DownloadManager.Session {
|
||||||
|
|
||||||
|
override val context get() = this@JobService
|
||||||
|
|
||||||
|
override fun attach(id: Int, builder: Notification.Builder) {
|
||||||
|
setNotification(params, id, builder.build(), JOB_END_NOTIFICATION_POLICY_REMOVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stop() {
|
||||||
jobFinished(params, false)
|
jobFinished(params, false)
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doWork() {
|
@SuppressLint("NewApi")
|
||||||
svc.fetchUpdate()?.let {
|
override fun onStartJob(params: JobParameters): Boolean {
|
||||||
Info.remote = it
|
return when (params.jobId) {
|
||||||
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
|
Const.ID.CHECK_UPDATE_JOB_ID -> checkUpdate(params)
|
||||||
Notifications.updateAvailable()
|
Const.ID.DOWNLOAD_JOB_ID -> downloadFile(params)
|
||||||
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopJob(params: JobParameters): Boolean {
|
override fun onStopJob(params: JobParameters?) = false
|
||||||
job.cancel()
|
|
||||||
return false
|
@TargetApi(value = 34)
|
||||||
|
private fun downloadFile(params: JobParameters): Boolean {
|
||||||
|
params.transientExtras.classLoader = Subject::class.java.classLoader
|
||||||
|
val subject = params.transientExtras
|
||||||
|
.getParcelable(DownloadManager.SUBJECT_KEY, Subject::class.java) ?:
|
||||||
|
return false
|
||||||
|
|
||||||
|
val session = mSession?.also {
|
||||||
|
it.params = params
|
||||||
|
} ?: run {
|
||||||
|
Session(params).also { mSession = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
val dm = mDm?.also {
|
||||||
|
it.reattach()
|
||||||
|
} ?: run {
|
||||||
|
DownloadManager(session).also { mDm = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
dm.download(subject)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkUpdate(params: JobParameters): Boolean {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
ServiceLocator.networkService.fetchUpdate()?.let {
|
||||||
|
Info.remote = it
|
||||||
|
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
|
||||||
|
Notifications.updateAvailable()
|
||||||
|
jobFinished(params, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -47,14 +91,14 @@ class JobService : BaseJobService() {
|
|||||||
val scheduler = context.getSystemService<JobScheduler>() ?: return
|
val scheduler = context.getSystemService<JobScheduler>() ?: return
|
||||||
if (Config.checkUpdate) {
|
if (Config.checkUpdate) {
|
||||||
val cmp = JobService::class.java.cmp(context.packageName)
|
val cmp = JobService::class.java.cmp(context.packageName)
|
||||||
val info = JobInfo.Builder(Const.ID.JOB_SERVICE_ID, cmp)
|
val info = JobInfo.Builder(Const.ID.CHECK_UPDATE_JOB_ID, cmp)
|
||||||
.setPeriodic(TimeUnit.HOURS.toMillis(12))
|
.setPeriodic(TimeUnit.HOURS.toMillis(12))
|
||||||
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||||
.setRequiresDeviceIdle(true)
|
.setRequiresDeviceIdle(true)
|
||||||
.build()
|
.build()
|
||||||
scheduler.schedule(info)
|
scheduler.schedule(info)
|
||||||
} else {
|
} else {
|
||||||
scheduler.cancel(Const.ID.JOB_SERVICE_ID)
|
scheduler.cancel(Const.ID.CHECK_UPDATE_JOB_ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,11 @@ package com.topjohnwu.magisk.core
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.core.content.IntentCompat
|
||||||
import com.topjohnwu.magisk.core.base.BaseReceiver
|
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
|
import com.topjohnwu.magisk.core.download.DownloadManager
|
||||||
|
import com.topjohnwu.magisk.core.download.Subject
|
||||||
import com.topjohnwu.magisk.view.Notifications
|
import com.topjohnwu.magisk.view.Notifications
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -35,6 +38,12 @@ open class Receiver : BaseReceiver() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (intent.action ?: return) {
|
when (intent.action ?: return) {
|
||||||
|
DownloadManager.ACTION -> {
|
||||||
|
IntentCompat.getParcelableExtra(
|
||||||
|
intent, DownloadManager.SUBJECT_KEY, Subject::class.java)?.let {
|
||||||
|
DownloadManager.start(context, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
Intent.ACTION_PACKAGE_REPLACED -> {
|
Intent.ACTION_PACKAGE_REPLACED -> {
|
||||||
// This will only work pre-O
|
// This will only work pre-O
|
||||||
if (Config.suReAuth)
|
if (Config.suReAuth)
|
||||||
|
@ -4,19 +4,26 @@ import android.Manifest
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.job.JobInfo
|
||||||
|
import android.app.job.JobScheduler
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.collection.SparseArrayCompat
|
import androidx.collection.SparseArrayCompat
|
||||||
import androidx.collection.isNotEmpty
|
import androidx.collection.isNotEmpty
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.StubApk
|
import com.topjohnwu.magisk.StubApk
|
||||||
import com.topjohnwu.magisk.core.ActivityTracker
|
import com.topjohnwu.magisk.core.ActivityTracker
|
||||||
|
import com.topjohnwu.magisk.core.Const
|
||||||
import com.topjohnwu.magisk.core.Info
|
import com.topjohnwu.magisk.core.Info
|
||||||
|
import com.topjohnwu.magisk.core.JobService
|
||||||
import com.topjohnwu.magisk.core.base.BaseActivity
|
import com.topjohnwu.magisk.core.base.BaseActivity
|
||||||
|
import com.topjohnwu.magisk.core.cmp
|
||||||
import com.topjohnwu.magisk.core.di.ServiceLocator
|
import com.topjohnwu.magisk.core.di.ServiceLocator
|
||||||
import com.topjohnwu.magisk.core.intent
|
import com.topjohnwu.magisk.core.intent
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
@ -55,7 +62,7 @@ class DownloadManager(
|
|||||||
interface Session {
|
interface Session {
|
||||||
val context: Context
|
val context: Context
|
||||||
|
|
||||||
fun attach(id: Int, notification: Notification.Builder)
|
fun attach(id: Int, builder: Notification.Builder)
|
||||||
fun stop()
|
fun stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,9 +86,16 @@ class DownloadManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createIntent(context: Context, subject: Subject) =
|
private fun createIntent(context: Context, subject: Subject) =
|
||||||
context.intent<com.topjohnwu.magisk.core.Service>()
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
.setAction(ACTION)
|
context.intent<com.topjohnwu.magisk.core.Receiver>()
|
||||||
.putExtra(SUBJECT_KEY, subject)
|
.setAction(ACTION)
|
||||||
|
.putExtra(SUBJECT_KEY, subject)
|
||||||
|
} else {
|
||||||
|
context.intent<com.topjohnwu.magisk.core.Service>()
|
||||||
|
.setAction(ACTION)
|
||||||
|
.putExtra(SUBJECT_KEY, subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
fun getPendingIntent(context: Context, subject: Subject): PendingIntent {
|
||||||
@ -89,7 +103,13 @@ class DownloadManager(
|
|||||||
PendingIntent.FLAG_UPDATE_CURRENT or
|
PendingIntent.FLAG_UPDATE_CURRENT or
|
||||||
PendingIntent.FLAG_ONE_SHOT
|
PendingIntent.FLAG_ONE_SHOT
|
||||||
val intent = createIntent(context, subject)
|
val intent = createIntent(context, subject)
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
return if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
// On API 34+, download tasks are handled with a user-initiated job.
|
||||||
|
// However, there is no way to schedule a new job directly with a pending intent.
|
||||||
|
// As a workaround, we send the subject to a broadcast receiver and have it
|
||||||
|
// schedule the job for us.
|
||||||
|
PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)
|
||||||
|
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||||
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)
|
||||||
} else {
|
} else {
|
||||||
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
PendingIntent.getService(context, REQUEST_CODE, intent, flag)
|
||||||
@ -97,15 +117,29 @@ class DownloadManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
fun start(activity: BaseActivity, subject: Subject) {
|
fun startWithActivity(activity: BaseActivity, subject: Subject) {
|
||||||
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {
|
||||||
// Always download regardless of notification permission status
|
// Always download regardless of notification permission status
|
||||||
val app = activity.applicationContext
|
start(activity.applicationContext, subject)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
}
|
||||||
app.startForegroundService(createIntent(app, subject))
|
}
|
||||||
} else {
|
|
||||||
app.startService(createIntent(app, subject))
|
fun start(context: Context, subject: Subject) {
|
||||||
}
|
if (Build.VERSION.SDK_INT >= 34) {
|
||||||
|
val scheduler = context.getSystemService<JobScheduler>()!!
|
||||||
|
val cmp = JobService::class.java.cmp(context.packageName)
|
||||||
|
val extras = Bundle()
|
||||||
|
extras.putParcelable(SUBJECT_KEY, subject)
|
||||||
|
val info = JobInfo.Builder(Const.ID.DOWNLOAD_JOB_ID, cmp)
|
||||||
|
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
||||||
|
.setUserInitiated(true)
|
||||||
|
.setTransientExtras(extras)
|
||||||
|
.build()
|
||||||
|
scheduler.schedule(info)
|
||||||
|
} else if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
context.startForegroundService(createIntent(context, subject))
|
||||||
|
} else {
|
||||||
|
context.startService(createIntent(context, subject))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,6 +174,12 @@ class DownloadManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun reattach() {
|
||||||
|
val builder = notifications[attachedId] ?: return
|
||||||
|
session.attach(attachedId, builder)
|
||||||
|
}
|
||||||
|
|
||||||
private val notifications = SparseArrayCompat<Notification.Builder>()
|
private val notifications = SparseArrayCompat<Notification.Builder>()
|
||||||
private var attachedId = -1
|
private var attachedId = -1
|
||||||
|
|
||||||
@ -205,15 +245,12 @@ class DownloadManager(
|
|||||||
// There are still remaining notifications, pick one and attach to the session
|
// There are still remaining notifications, pick one and attach to the session
|
||||||
val anotherId = notifications.keyAt(0)
|
val anotherId = notifications.keyAt(0)
|
||||||
val notification = notifications.valueAt(0)
|
val notification = notifications.valueAt(0)
|
||||||
// Attaching a new notification will automatically remove the current one
|
|
||||||
attachNotification(anotherId, notification)
|
attachNotification(anotherId, notification)
|
||||||
} else {
|
} else {
|
||||||
// No more notifications left, terminate the session
|
// No more notifications left, terminate the session
|
||||||
attachedId = -1
|
attachedId = -1
|
||||||
session.stop()
|
session.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ sealed class Subject : Parcelable {
|
|||||||
override val notifyId: Int = Notifications.nextId(),
|
override val notifyId: Int = Notifications.nextId(),
|
||||||
override val title: String = UUID.randomUUID().toString().substring(0, 6)
|
override val title: String = UUID.randomUUID().toString().substring(0, 6)
|
||||||
) : Subject() {
|
) : Subject() {
|
||||||
override val url get() = "http://link.testfile.org/150MB"
|
override val url get() = "https://link.testfile.org/250MB"
|
||||||
override val file get() = File("/dev/null").toUri()
|
override val file get() = File("/dev/null").toUri()
|
||||||
override val autoLaunch get() = false
|
override val autoLaunch get() = false
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class ManagerInstallDialog : MarkDownDialog() {
|
|||||||
setCancelable(true)
|
setCancelable(true)
|
||||||
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
setButton(MagiskDialog.ButtonType.POSITIVE) {
|
||||||
text = R.string.install
|
text = R.string.install
|
||||||
onClick { DownloadManager.start(activity, Subject.App()) }
|
onClick { DownloadManager.startWithActivity(activity, Subject.App()) }
|
||||||
}
|
}
|
||||||
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
setButton(MagiskDialog.ButtonType.NEGATIVE) {
|
||||||
text = android.R.string.cancel
|
text = android.R.string.cancel
|
||||||
|
@ -24,7 +24,7 @@ class OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog
|
|||||||
fun download(install: Boolean) {
|
fun download(install: Boolean) {
|
||||||
val action = if (install) Action.Flash else Action.Download
|
val action = if (install) Action.Flash else Action.Download
|
||||||
val subject = Subject.Module(item, action)
|
val subject = Subject.Module(item, action)
|
||||||
DownloadManager.start(activity, subject)
|
DownloadManager.startWithActivity(activity, subject)
|
||||||
}
|
}
|
||||||
|
|
||||||
val title = context.getString(R.string.repo_install_title,
|
val title = context.getString(R.string.repo_install_title,
|
||||||
|
@ -26,6 +26,6 @@ android.injected.testOnly=false
|
|||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
|
|
||||||
# Magisk
|
# Magisk
|
||||||
magisk.stubVersion=38
|
magisk.stubVersion=39
|
||||||
magisk.versionCode=27001
|
magisk.versionCode=27001
|
||||||
magisk.ondkVersion=r26.3
|
magisk.ondkVersion=r26.3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user