Restructure notification channels by intent and importance

Notifications were too intrusive: install-success and export sat on
IMPORTANT_HIGH channels and produced heads-up alerts for every app,
while genuine download failures were buried in the IMPORTANCE_MIN
downloads channel where they were easily missed.

Restructure the channels around user intent:

- Download progress -> MIN, silent, no badge
- Installed apps    -> LOW, silent (was HIGH)
- Updates available -> DEFAULT, silent
- App export        -> LOW, silent (was HIGH)
- Errors & alerts   -> HIGH (new, the only heads-up channel)

Failed downloads, installer-status errors and the unarchive auth prompt
now route to the high-importance alerts channel so they aren't lost,
while successful installs/exports stay quiet.

Android ignores importance edits on an already-created channel, so the
install/export channels get new IDs and the retired IDs (including the
folded-in account channel) are deleted on next launch.
This commit is contained in:
Rahul Patel
2026-05-30 17:54:59 +05:30
parent a68d1b276d
commit 012f4dcc26
3 changed files with 78 additions and 33 deletions

View File

@@ -42,11 +42,22 @@ object Constants {
const val UPDATE_URL_NIGHTLY = const val UPDATE_URL_NIGHTLY =
"https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json" "https://auroraoss.com/downloads/AuroraStore/Feeds/nightly_feed.json"
const val NOTIFICATION_CHANNEL_EXPORT = "NOTIFICATION_CHANNEL_EXPORT" // Channel IDs carry a version suffix where the importance changed from a previous
const val NOTIFICATION_CHANNEL_INSTALL = "NOTIFICATION_CHANNEL_INSTALL" // release: Android ignores importance edits on an already-created channel, so a new ID
// is the only way to roll out a lower importance. Retired IDs are listed in
// [LEGACY_NOTIFICATION_CHANNELS] so they can be deleted on next launch.
const val NOTIFICATION_CHANNEL_EXPORT = "NOTIFICATION_CHANNEL_EXPORT_V2"
const val NOTIFICATION_CHANNEL_INSTALL = "NOTIFICATION_CHANNEL_INSTALLED"
const val NOTIFICATION_CHANNEL_DOWNLOADS = "NOTIFICATION_CHANNEL_DOWNLOADS" const val NOTIFICATION_CHANNEL_DOWNLOADS = "NOTIFICATION_CHANNEL_DOWNLOADS"
const val NOTIFICATION_CHANNEL_UPDATES = "NOTIFICATION_CHANNEL_UPDATES" const val NOTIFICATION_CHANNEL_UPDATES = "NOTIFICATION_CHANNEL_UPDATES"
const val NOTIFICATION_CHANNEL_ACCOUNT = "NOTIFICATION_CHANNEL_ACCOUNT" const val NOTIFICATION_CHANNEL_ALERTS = "NOTIFICATION_CHANNEL_ALERTS"
// Channels removed or superseded by a higher-versioned ID; deleted on next launch.
val LEGACY_NOTIFICATION_CHANNELS = listOf(
"NOTIFICATION_CHANNEL_EXPORT",
"NOTIFICATION_CHANNEL_INSTALL",
"NOTIFICATION_CHANNEL_ACCOUNT"
)
const val GITLAB_URL = "https://gitlab.com/AuroraOSS/AuroraStore" const val GITLAB_URL = "https://gitlab.com/AuroraOSS/AuroraStore"
const val URL_DISPENSER = "https://auroraoss.com/api/auth" const val URL_DISPENSER = "https://auroraoss.com/api/auth"

View File

@@ -32,38 +32,42 @@ object NotificationUtil {
fun createNotificationChannel(context: Context) { fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = context.getSystemService<NotificationManager>() val notificationManager = context.getSystemService<NotificationManager>()!!
// Drop channels retired or superseded by a lower-importance replacement, so the
// user no longer sees stale entries in system settings.
Constants.LEGACY_NOTIFICATION_CHANNELS.forEach {
notificationManager.deleteNotificationChannel(it)
}
val channels = ArrayList<NotificationChannel>() val channels = ArrayList<NotificationChannel>()
channels.add(
NotificationChannel( // Quiet, ongoing progress for active downloads. MIN keeps it collapsed and out
Constants.NOTIFICATION_CHANNEL_INSTALL, // of the status bar where possible.
context.getString(R.string.notification_channel_install),
NotificationManager.IMPORTANCE_HIGH
).apply {
setSound(null, null)
}
)
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_EXPORT,
context.getString(R.string.notification_channel_export),
NotificationManager.IMPORTANCE_HIGH
)
)
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_ACCOUNT,
context.getString(R.string.notification_channel_account),
NotificationManager.IMPORTANCE_HIGH
)
)
channels.add( channels.add(
NotificationChannel( NotificationChannel(
Constants.NOTIFICATION_CHANNEL_DOWNLOADS, Constants.NOTIFICATION_CHANNEL_DOWNLOADS,
context.getString(R.string.notification_channel_downloads), context.getString(R.string.notification_channel_downloads),
NotificationManager.IMPORTANCE_MIN NotificationManager.IMPORTANCE_MIN
) ).apply {
setSound(null, null)
setShowBadge(false)
}
) )
// Silent confirmation that an app finished installing/updating. LOW so it lands
// in the shade without buzzing for every app during a bulk update.
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_INSTALL,
context.getString(R.string.notification_channel_install),
NotificationManager.IMPORTANCE_LOW
).apply {
setSound(null, null)
}
)
// New updates are available to install. DEFAULT but silent.
channels.add( channels.add(
NotificationChannel( NotificationChannel(
Constants.NOTIFICATION_CHANNEL_UPDATES, Constants.NOTIFICATION_CHANNEL_UPDATES,
@@ -73,7 +77,29 @@ object NotificationUtil {
setSound(null, null) setSound(null, null)
} }
) )
notificationManager!!.createNotificationChannels(channels)
// Background app export. LOW since it's user-initiated and non-urgent.
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_EXPORT,
context.getString(R.string.notification_channel_export),
NotificationManager.IMPORTANCE_LOW
).apply {
setSound(null, null)
}
)
// Things the user genuinely needs to act on: failed downloads/installs, pending
// user action and authentication prompts. HIGH so these aren't missed.
channels.add(
NotificationChannel(
Constants.NOTIFICATION_CHANNEL_ALERTS,
context.getString(R.string.notification_channel_alerts),
NotificationManager.IMPORTANCE_HIGH
)
)
notificationManager.createNotificationChannels(channels)
} }
} }
@@ -91,7 +117,14 @@ object NotificationUtil {
largeIcon: Bitmap? = null, largeIcon: Bitmap? = null,
message: String? = null message: String? = null
): Notification { ): Notification {
val builder = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_DOWNLOADS) // Terminal failures go to the high-importance alerts channel so they aren't lost in
// the silent downloads channel; everything else stays quiet.
val channelId = if (download.status == DownloadStatus.FAILED) {
Constants.NOTIFICATION_CHANNEL_ALERTS
} else {
Constants.NOTIFICATION_CHANNEL_DOWNLOADS
}
val builder = NotificationCompat.Builder(context, channelId)
builder.setSmallIcon(R.drawable.ic_notification_outlined) builder.setSmallIcon(R.drawable.ic_notification_outlined)
builder.setContentTitle(download.displayName) builder.setContentTitle(download.displayName)
builder.setContentIntent(getContentIntentForDetails(context, download.packageName)) builder.setContentIntent(getContentIntentForDetails(context, download.packageName))
@@ -197,11 +230,12 @@ object NotificationUtil {
packageName: String, packageName: String,
displayName: String, displayName: String,
content: String? content: String?
): Notification = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_INSTALL) ): Notification = NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERTS)
.setSmallIcon(R.drawable.ic_install) .setSmallIcon(R.drawable.ic_install)
.setContentTitle(displayName) .setContentTitle(displayName)
.setContentText(content) .setContentText(content)
.setContentIntent(getContentIntentForDetails(context, packageName)) .setContentIntent(getContentIntentForDetails(context, packageName))
.setCategory(Notification.CATEGORY_ERROR)
.build() .build()
fun getUpdateNotification(context: Context): Notification = fun getUpdateNotification(context: Context): Notification =
@@ -289,7 +323,7 @@ object NotificationUtil {
.build() .build()
fun getUnarchiveAuthNotification(context: Context, packageName: String): Notification = fun getUnarchiveAuthNotification(context: Context, packageName: String): Notification =
NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ACCOUNT) NotificationCompat.Builder(context, Constants.NOTIFICATION_CHANNEL_ALERTS)
.setSmallIcon(R.drawable.ic_account) .setSmallIcon(R.drawable.ic_account)
.setContentTitle(context.getString(R.string.authentication_required_title)) .setContentTitle(context.getString(R.string.authentication_required_title))
.setContentText(context.getString(R.string.authentication_required_unarchive)) .setContentText(context.getString(R.string.authentication_required_unarchive))

View File

@@ -211,7 +211,7 @@
<string name="device_miui_extra">"Optionally you can choose Native installer, but then you can not install bundled (split) APKs, so choice is yours."</string> <string name="device_miui_extra">"Optionally you can choose Native installer, but then you can not install bundled (split) APKs, so choice is yours."</string>
<string name="dialog_title_self_update">"New update available"</string> <string name="dialog_title_self_update">"New update available"</string>
<string name="dialog_desc_native_split">"You can not install bundled (split) apps via Native Installer. Change your installer to Session, Services or Root."</string> <string name="dialog_desc_native_split">"You can not install bundled (split) apps via Native Installer. Change your installer to Session, Services or Root."</string>
<string name="notification_channel_account">Account-related notification</string> <string name="notification_channel_alerts">Errors &amp; alerts</string>
<string name="notification_channel_export">App export notification</string> <string name="notification_channel_export">App export notification</string>
<string name="notification_channel_install">Install notification</string> <string name="notification_channel_install">Install notification</string>
<string name="notification_channel_downloads">Downloads notification</string> <string name="notification_channel_downloads">Downloads notification</string>