mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-24 16:27:37 +00:00
Show notification after app upgrade
This commit is contained in:
parent
2414d5d7f5
commit
256ff31d11
@ -36,7 +36,7 @@ public final class APKInstall {
|
||||
// @WorkerThread
|
||||
public static void install(Context context, File apk) {
|
||||
try (var src = new FileInputStream(apk);
|
||||
var out = openStream(context, true)) {
|
||||
var out = openStream(context)) {
|
||||
if (out != null)
|
||||
transfer(src, out);
|
||||
} catch (IOException e) {
|
||||
@ -44,7 +44,7 @@ public final class APKInstall {
|
||||
}
|
||||
}
|
||||
|
||||
public static OutputStream openStream(Context context, boolean silent) {
|
||||
public static OutputStream openStream(Context context) {
|
||||
//noinspection InlinedApi
|
||||
var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;
|
||||
var intent = new Intent(ACTION_SESSION_UPDATE).setPackage(context.getPackageName());
|
||||
@ -52,7 +52,7 @@ public final class APKInstall {
|
||||
|
||||
var installer = context.getPackageManager().getPackageInstaller();
|
||||
var params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
|
||||
if (silent && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);
|
||||
}
|
||||
try {
|
||||
|
@ -46,6 +46,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
<action android:name="android.intent.action.UID_REMOVED" />
|
||||
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
|
@ -61,6 +61,7 @@ object Config : PreferenceModel, DBConfig {
|
||||
const val BOOT_ID = "boot_id"
|
||||
const val ASKED_HOME = "asked_home"
|
||||
const val DOH = "doh"
|
||||
const val SHOW_UPDATE_DONE = "update_done"
|
||||
}
|
||||
|
||||
object Value {
|
||||
@ -133,6 +134,7 @@ object Config : PreferenceModel, DBConfig {
|
||||
var suTapjack by preference(Key.SU_TAPJACK, true)
|
||||
var checkUpdate by preference(Key.CHECK_UPDATES, true)
|
||||
var doh by preference(Key.DOH, false)
|
||||
var showUpdateDone by preference(Key.SHOW_UPDATE_DONE, false)
|
||||
var showSystemApp by preference(Key.SHOW_SYSTEM_APP, false)
|
||||
|
||||
var customChannelUrl by preference(Key.CUSTOM_CHANNEL, "")
|
||||
|
@ -37,8 +37,6 @@ object Const {
|
||||
|
||||
object ID {
|
||||
const val JOB_SERVICE_ID = 7
|
||||
const val UPDATE_NOTIFICATION_CHANNEL = "update"
|
||||
const val PROGRESS_NOTIFICATION_CHANNEL = "progress"
|
||||
}
|
||||
|
||||
object Url {
|
||||
|
@ -23,16 +23,20 @@ class JobService : BaseJobService() {
|
||||
override fun onStartJob(params: JobParameters): Boolean {
|
||||
val coroutineScope = CoroutineScope(Dispatchers.IO + job)
|
||||
coroutineScope.launch {
|
||||
svc.fetchUpdate()?.run {
|
||||
Info.remote = this
|
||||
if (Info.env.isActive && BuildConfig.VERSION_CODE < magisk.versionCode)
|
||||
Notifications.managerUpdate(this@JobService)
|
||||
}
|
||||
doWork()
|
||||
jobFinished(params, false)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private suspend fun doWork() {
|
||||
svc.fetchUpdate()?.let {
|
||||
Info.remote = it
|
||||
if (Info.env.isActive && BuildConfig.VERSION_CODE < it.magisk.versionCode)
|
||||
Notifications.updateAvailable(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStopJob(params: JobParameters): Boolean {
|
||||
job.cancel()
|
||||
return false
|
||||
|
@ -5,6 +5,7 @@ import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import com.topjohnwu.magisk.core.base.BaseReceiver
|
||||
import com.topjohnwu.magisk.di.ServiceLocator
|
||||
import com.topjohnwu.magisk.view.Notifications
|
||||
import com.topjohnwu.magisk.view.Shortcuts
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@ -45,6 +46,12 @@ open class Receiver : BaseReceiver() {
|
||||
getPkg(intent)?.let { Shell.su("magisk --denylist rm $it").submit() }
|
||||
}
|
||||
Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)
|
||||
Intent.ACTION_MY_PACKAGE_REPLACED -> {
|
||||
if (Config.showUpdateDone) {
|
||||
Notifications.updateDone(context)
|
||||
Config.showUpdateDone = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,7 @@ import androidx.core.net.toFile
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.StubApk
|
||||
import com.topjohnwu.magisk.core.ActivityTracker
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.intent
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.core.tasks.HideAPK
|
||||
import com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream
|
||||
import com.topjohnwu.magisk.ktx.copyAndClose
|
||||
@ -75,32 +72,49 @@ class DownloadService : NotificationService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun openApkSession(): OutputStream {
|
||||
Config.showUpdateDone = true
|
||||
return APKInstall.openStream(this)
|
||||
}
|
||||
|
||||
private suspend fun handleApp(stream: InputStream, subject: Subject.App) {
|
||||
fun write(output: OutputStream) {
|
||||
fun writeTee(output: OutputStream) {
|
||||
val external = subject.externalFile.outputStream()
|
||||
stream.copyAndClose(TeeOutputStream(external, output))
|
||||
}
|
||||
|
||||
if (isRunningAsStub) {
|
||||
val apk = subject.file.toFile()
|
||||
val id = subject.notifyId
|
||||
val updateApk = StubApk.update(this)
|
||||
try {
|
||||
write(StubApk.update(this).outputStream())
|
||||
// Download full APK to stub update path
|
||||
writeTee(updateApk.outputStream())
|
||||
|
||||
if (Info.stub!!.version < subject.stub.versionCode) {
|
||||
// Also upgrade stub
|
||||
update(id) {
|
||||
update(subject.notifyId) {
|
||||
it.setProgress(0, 0, true)
|
||||
.setContentTitle(getString(R.string.hide_app_title))
|
||||
.setContentText("")
|
||||
}
|
||||
|
||||
// Download
|
||||
val apk = subject.file.toFile()
|
||||
service.fetchFile(subject.stub.link).byteStream().writeTo(apk)
|
||||
|
||||
// Patch
|
||||
val patched = File(apk.parent, "patched.apk")
|
||||
val label = applicationInfo.nonLocalizedLabel
|
||||
if (!HideAPK.patch(this, apk, patched, packageName, label)) {
|
||||
throw IOException("HideAPK patch error")
|
||||
}
|
||||
apk.delete()
|
||||
patched.renameTo(apk)
|
||||
|
||||
// Install
|
||||
val receiver = APKInstall.register(this, null, null)
|
||||
patched.inputStream().copyAndClose(openApkSession())
|
||||
subject.intent = receiver.waitIntent()
|
||||
|
||||
patched.delete()
|
||||
} else {
|
||||
ActivityTracker.foreground?.let {
|
||||
// Relaunch the process if we are foreground
|
||||
@ -112,13 +126,16 @@ class DownloadService : NotificationService() {
|
||||
return
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
StubApk.update(this).delete()
|
||||
}
|
||||
// If any error occurred, do not let stub load the new APK
|
||||
updateApk.delete()
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
val receiver = APKInstall.register(this, null, null)
|
||||
write(APKInstall.openStream(this, false))
|
||||
writeTee(openApkSession())
|
||||
subject.intent = receiver.waitIntent()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleModule(src: InputStream, file: Uri) {
|
||||
val input = ZipInputStream(src.buffered())
|
||||
|
@ -15,7 +15,10 @@ import com.topjohnwu.magisk.MainDirections
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.arch.BaseMainActivity
|
||||
import com.topjohnwu.magisk.arch.BaseViewModel
|
||||
import com.topjohnwu.magisk.core.*
|
||||
import com.topjohnwu.magisk.core.Config
|
||||
import com.topjohnwu.magisk.core.Const
|
||||
import com.topjohnwu.magisk.core.Info
|
||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||
import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding
|
||||
import com.topjohnwu.magisk.di.viewModel
|
||||
import com.topjohnwu.magisk.ktx.startAnimations
|
||||
@ -48,10 +51,11 @@ class MainActivity : BaseMainActivity<ActivityMainMd2Binding>() {
|
||||
setContentView()
|
||||
showUnsupportedMessage()
|
||||
askForHomeShortcut()
|
||||
Config.showUpdateDone = false
|
||||
|
||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
|
||||
navigation?.addOnDestinationChangedListener { _, destination, _ ->
|
||||
navigation.addOnDestinationChangedListener { _, destination, _ ->
|
||||
isRootFragment = when (destination.id) {
|
||||
R.id.homeFragment,
|
||||
R.id.modulesFragment,
|
||||
|
@ -1,15 +1,16 @@
|
||||
package com.topjohnwu.magisk.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build.VERSION.SDK_INT
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.drawable.toIcon
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.core.Const.ID.PROGRESS_NOTIFICATION_CHANNEL
|
||||
import com.topjohnwu.magisk.core.Const.ID.UPDATE_NOTIFICATION_CHANNEL
|
||||
import com.topjohnwu.magisk.core.download.DownloadService
|
||||
import com.topjohnwu.magisk.core.download.Subject
|
||||
import com.topjohnwu.magisk.di.AppContext
|
||||
@ -21,51 +22,75 @@ object Notifications {
|
||||
|
||||
val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! }
|
||||
|
||||
private const val APK_UPDATE_NOTIFICATION_ID = 5
|
||||
private val nextId = AtomicInteger(APK_UPDATE_NOTIFICATION_ID)
|
||||
private const val APP_UPDATED_NOTIFICATION_ID = 4
|
||||
private const val APP_UPDATE_NOTIFICATION_ID = 5
|
||||
|
||||
private const val UPDATE_CHANNEL = "update"
|
||||
private const val PROGRESS_CHANNEL = "progress"
|
||||
private const val UPDATED_CHANNEL = "updated"
|
||||
|
||||
private val nextId = AtomicInteger(APP_UPDATE_NOTIFICATION_ID)
|
||||
|
||||
fun setup(context: Context) {
|
||||
if (SDK_INT >= 26) {
|
||||
val channel = NotificationChannel(UPDATE_NOTIFICATION_CHANNEL,
|
||||
val channel = NotificationChannel(UPDATE_CHANNEL,
|
||||
context.getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)
|
||||
val channel2 = NotificationChannel(PROGRESS_NOTIFICATION_CHANNEL,
|
||||
val channel2 = NotificationChannel(PROGRESS_CHANNEL,
|
||||
context.getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)
|
||||
mgr.createNotificationChannels(listOf(channel, channel2))
|
||||
val channel3 = NotificationChannel(UPDATED_CHANNEL,
|
||||
context.getString(R.string.updated_channel), NotificationManager.IMPORTANCE_HIGH)
|
||||
mgr.createNotificationChannels(listOf(channel, channel2, channel3))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBuilder(context: Context): Notification.Builder {
|
||||
return Notification.Builder(context).apply {
|
||||
val bitmap = context.getBitmap(R.drawable.ic_magisk_outline)
|
||||
setLargeIcon(bitmap)
|
||||
if (SDK_INT >= 26) {
|
||||
setSmallIcon(bitmap.toIcon())
|
||||
setChannelId(UPDATE_NOTIFICATION_CHANNEL)
|
||||
@SuppressLint("InlinedApi")
|
||||
fun updateDone(context: Context) {
|
||||
val pm = context.packageManager
|
||||
val intent = pm.getLaunchIntentForPackage(context.packageName) ?: return
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
val pending = PendingIntent.getActivity(context, 0, intent, flag)
|
||||
val builder = if (SDK_INT >= 26) {
|
||||
Notification.Builder(context, UPDATED_CHANNEL)
|
||||
.setSmallIcon(context.getBitmap(R.drawable.ic_magisk_outline).toIcon())
|
||||
} else {
|
||||
setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
}
|
||||
Notification.Builder(context).setPriority(Notification.PRIORITY_HIGH)
|
||||
.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
}
|
||||
.setContentIntent(pending)
|
||||
.setContentTitle(context.getText(R.string.updated_title))
|
||||
.setContentText(context.getText(R.string.updated_text))
|
||||
.setAutoCancel(true)
|
||||
mgr.notify(APP_UPDATED_NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
fun managerUpdate(context: Context) {
|
||||
fun updateAvailable(context: Context) {
|
||||
val intent = DownloadService.getPendingIntent(context, Subject.App())
|
||||
|
||||
val builder = updateBuilder(context)
|
||||
val bitmap = context.getBitmap(R.drawable.ic_magisk_outline)
|
||||
val builder = if (SDK_INT >= 26) {
|
||||
Notification.Builder(context, UPDATE_CHANNEL)
|
||||
.setSmallIcon(bitmap.toIcon())
|
||||
} else {
|
||||
Notification.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_magisk_outline)
|
||||
}
|
||||
.setLargeIcon(bitmap)
|
||||
.setContentTitle(context.getString(R.string.magisk_update_title))
|
||||
.setContentText(context.getString(R.string.manager_download_install))
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(intent)
|
||||
|
||||
mgr.notify(APK_UPDATE_NOTIFICATION_ID, builder.build())
|
||||
mgr.notify(APP_UPDATE_NOTIFICATION_ID, builder.build())
|
||||
}
|
||||
|
||||
fun progress(context: Context, title: CharSequence): Notification.Builder {
|
||||
val builder = if (SDK_INT >= 26) {
|
||||
Notification.Builder(context, PROGRESS_NOTIFICATION_CHANNEL)
|
||||
Notification.Builder(context, PROGRESS_CHANNEL)
|
||||
} else {
|
||||
Notification.Builder(context).setPriority(Notification.PRIORITY_LOW)
|
||||
}
|
||||
builder.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentTitle(title)
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true)
|
||||
|
@ -190,9 +190,12 @@
|
||||
<!--Notifications-->
|
||||
<string name="update_channel">Magisk Updates</string>
|
||||
<string name="progress_channel">Progress Notifications</string>
|
||||
<string name="updated_channel">Update Complete</string>
|
||||
<string name="download_complete">Download complete</string>
|
||||
<string name="download_file_error">Error downloading file</string>
|
||||
<string name="magisk_update_title">Magisk Update Available!</string>
|
||||
<string name="updated_title">Magisk Updated</string>
|
||||
<string name="updated_text">Tap to open app</string>
|
||||
|
||||
<!--Toasts, Dialogs-->
|
||||
<string name="yes">Yes</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user