Show notification after app upgrade

This commit is contained in:
topjohnwu 2022-02-13 18:35:35 -08:00
parent 2414d5d7f5
commit 256ff31d11
10 changed files with 109 additions and 48 deletions

View File

@ -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 {

View File

@ -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" />

View File

@ -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, "")

View File

@ -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 {

View File

@ -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

View File

@ -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
}
}
}
}
}

View File

@ -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())

View File

@ -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,

View File

@ -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)

View File

@ -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>