From 256ff31d11b210677961ee3e1ad823b7a6a65070 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 13 Feb 2022 18:35:35 -0800 Subject: [PATCH] Show notification after app upgrade --- .../topjohnwu/magisk/utils/APKInstall.java | 6 +- app/src/main/AndroidManifest.xml | 1 + .../java/com/topjohnwu/magisk/core/Config.kt | 2 + .../java/com/topjohnwu/magisk/core/Const.kt | 2 - .../com/topjohnwu/magisk/core/JobService.kt | 14 ++-- .../com/topjohnwu/magisk/core/Receiver.kt | 7 ++ .../magisk/core/download/DownloadService.kt | 45 ++++++++---- .../com/topjohnwu/magisk/ui/MainActivity.kt | 8 ++- .../topjohnwu/magisk/view/Notifications.kt | 69 +++++++++++++------ app/src/main/res/values/strings.xml | 3 + 10 files changed, 109 insertions(+), 48 deletions(-) diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java index 490091000..5f30fa285 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java @@ -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 { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index be547f11a..0899e6ccd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,6 +46,7 @@ + diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Config.kt b/app/src/main/java/com/topjohnwu/magisk/core/Config.kt index e5b283002..af7cceb3e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Config.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Config.kt @@ -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, "") diff --git a/app/src/main/java/com/topjohnwu/magisk/core/Const.kt b/app/src/main/java/com/topjohnwu/magisk/core/Const.kt index 984a46a7b..c6408d3e5 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Const.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Const.kt @@ -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 { 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 29734db77..702504fee 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/JobService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/JobService.kt @@ -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 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 d500f91b8..fa986db96 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/Receiver.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/Receiver.kt @@ -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 + } + } } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt index f57778234..091023bbb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt @@ -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,12 +126,15 @@ 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) + writeTee(openApkSession()) + subject.intent = receiver.waitIntent() } - val receiver = APKInstall.register(this, null, null) - write(APKInstall.openStream(this, false)) - subject.intent = receiver.waitIntent() } private fun handleModule(src: InputStream, file: Uri) { diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt index 98b93745b..b2e9aa90b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt @@ -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() { 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, 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 59179b449..b253f4bd1 100644 --- a/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt +++ b/app/src/main/java/com/topjohnwu/magisk/view/Notifications.kt @@ -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()!! } - 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) - } else { - setSmallIcon(R.drawable.ic_magisk_outline) - } + @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 { + 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) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c85a0185a..afe34bd19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -190,9 +190,12 @@ Magisk Updates Progress Notifications + Update Complete Download complete Error downloading file Magisk Update Available! + Magisk Updated + Tap to open app Yes