From 4da7bafbee7ddfd5dc08a11b5af705ea1ee2d784 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 6 May 2026 10:18:46 -0300 Subject: [PATCH] Show notification after self-update because we aren't allowed to launch the app. So the user can at least re-launch us easily. --- .../main/kotlin/org/fdroid/MainActivity.kt | 6 ++++ .../kotlin/org/fdroid/NotificationManager.kt | 31 +++++++++++++++++++ .../org/fdroid/install/AppUpdateReceiver.kt | 16 ++++++++-- app/src/main/res/values/strings.xml | 4 +++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/fdroid/MainActivity.kt b/app/src/main/kotlin/org/fdroid/MainActivity.kt index 54f1550c4..5ec0ccba7 100644 --- a/app/src/main/kotlin/org/fdroid/MainActivity.kt +++ b/app/src/main/kotlin/org/fdroid/MainActivity.kt @@ -31,6 +31,7 @@ class MainActivity : AppCompatActivity() { val requestPermissionLauncher = registerForActivityResult(RequestPermission()) {} @Inject lateinit var settingsManager: SettingsManager + @Inject lateinit var notificationManager: NotificationManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -78,4 +79,9 @@ class MainActivity : AppCompatActivity() { // calling super seems to be needed, so the IntentListener gets informed super.onNewIntent(intent, caller) } + + override fun onResume() { + super.onResume() + notificationManager.cancelSelfUpdateNotification() + } } diff --git a/app/src/main/kotlin/org/fdroid/NotificationManager.kt b/app/src/main/kotlin/org/fdroid/NotificationManager.kt index 4ed97784f..cf12f74a8 100644 --- a/app/src/main/kotlin/org/fdroid/NotificationManager.kt +++ b/app/src/main/kotlin/org/fdroid/NotificationManager.kt @@ -12,11 +12,14 @@ import androidx.annotation.StringRes import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat.BigTextStyle +import androidx.core.app.NotificationCompat.CATEGORY_REMINDER import androidx.core.app.NotificationCompat.CATEGORY_SERVICE import androidx.core.app.NotificationCompat.PRIORITY_HIGH +import androidx.core.app.NotificationCompat.PRIORITY_MAX import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT import androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW +import androidx.core.app.NotificationManagerCompat.IMPORTANCE_MAX import androidx.core.content.ContextCompat.checkSelfPermission import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject @@ -40,10 +43,12 @@ constructor(@param:ApplicationContext private val context: Context) { const val NOTIFICATION_ID_APP_INSTALLS: Int = 1 const val NOTIFICATION_ID_APP_INSTALL_SUCCESS: Int = 2 const val NOTIFICATION_ID_APP_UPDATES_AVAILABLE: Int = 3 + const val NOTIFICATION_ID_SELF_UPDATE: Int = 4 private const val CHANNEL_UPDATES = "update-channel" private const val CHANNEL_INSTALLS = "install-channel" private const val CHANNEL_INSTALL_SUCCESS = "install-success-channel" private const val CHANNEL_UPDATES_AVAILABLE = "updates-available-channel" + private const val CHANNEL_SELF_UPDATE = "self-update-channel" } init { @@ -69,6 +74,10 @@ constructor(@param:ApplicationContext private val context: Context) { .setName(s(R.string.notification_channel_updates_available_title)) .setDescription(s(R.string.notification_channel_updates_available_description)) .build(), + NotificationChannelCompat.Builder(CHANNEL_SELF_UPDATE, IMPORTANCE_MAX) + .setName(s(R.string.notification_channel_self_update_title)) + .setDescription(s(R.string.notification_channel_self_update_description)) + .build(), ) nm.createNotificationChannelsCompat(channels) } @@ -181,6 +190,28 @@ constructor(@param:ApplicationContext private val context: Context) { return builder } + fun showSelfUpdateNotification() { + val pi = getMyAppsPendingIntent(context) + val app = context.getString(R.string.app_name) + val title = context.getString(R.string.notification_self_update_title, app) + val builder = + NotificationCompat.Builder(context, CHANNEL_SELF_UPDATE) + .setSmallIcon(R.drawable.ic_notification) + .setCategory(CATEGORY_REMINDER) + .setContentTitle(title) + .setPriority(PRIORITY_MAX) + .setContentIntent(pi) + .setAutoCancel(true) + val n = builder.build() + if (checkSelfPermission(context, POST_NOTIFICATIONS) == PERMISSION_GRANTED) { + nm.notify(NOTIFICATION_ID_SELF_UPDATE, n) + } + } + + fun cancelSelfUpdateNotification() { + nm.cancel(NOTIFICATION_ID_SELF_UPDATE) + } + private fun getMainActivityPendingIntent(context: Context): PendingIntent { val i = Intent(ACTION_MAIN).apply { setClass(context, MainActivity::class.java) } val flags = FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE diff --git a/app/src/main/kotlin/org/fdroid/install/AppUpdateReceiver.kt b/app/src/main/kotlin/org/fdroid/install/AppUpdateReceiver.kt index b85cf3e9a..75bd1d37a 100644 --- a/app/src/main/kotlin/org/fdroid/install/AppUpdateReceiver.kt +++ b/app/src/main/kotlin/org/fdroid/install/AppUpdateReceiver.kt @@ -5,14 +5,19 @@ import android.content.Context import android.content.Intent import android.content.Intent.ACTION_MY_PACKAGE_REPLACED import android.content.Intent.FLAG_ACTIVITY_NEW_TASK -import androidx.annotation.RequiresApi +import android.os.Build.VERSION.SDK_INT +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject import mu.KotlinLogging +import org.fdroid.NotificationManager +@AndroidEntryPoint class AppUpdateReceiver : BroadcastReceiver() { private val log = KotlinLogging.logger {} - @RequiresApi(35) + @Inject lateinit var notificationManager: NotificationManager + override fun onReceive(context: Context, intent: Intent) { if (intent.action != ACTION_MY_PACKAGE_REPLACED) { log.warn { "Unknown action: ${intent.action}" } @@ -23,12 +28,17 @@ class AppUpdateReceiver : BroadcastReceiver() { context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply { addFlags(FLAG_ACTIVITY_NEW_TASK) } - if (intent != null) { + if (intent == null) { + log.error { "Could not get launch intent for ourselves" } + } else { try { context.startActivity(intent) } catch (e: Exception) { log.error(e) { "Failed to start activity after update" } } } + // show notification on Android 10+, because we aren't allowed to start activity from background + // see: https://developer.android.com/guide/components/activities/background-starts + if (SDK_INT >= 29) notificationManager.showSelfUpdateNotification() } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ebfb085d..0570b9a1e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -955,6 +955,8 @@ This often occurs with apps installed via Google Play or other sources, if they Displays a notification after apps were installed automatically Available app updates Displays a notification after repositories were updated and app updates were found + Self-update + Notifies when the app updated itself and was closed, so you can launch it again easily Connecting to %1$s… Downloaded %1$s from %2$s @@ -978,6 +980,8 @@ This often occurs with apps installed via Google Play or other sources, if they Needs user confirmation: Installed: Tap to confirm + + %s was updated. Tap to open Category %1$s