Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,64 @@ import eu.darken.amply.main.ui.MainActivity
object SessionNotifications {
const val SESSION_ID = 4101
private const val RECOVERY_ID = 4102
// Channel ids are invisible to the user and permanent — changing one resets that channel's
// settings — so this keeps its original id while its display name has moved on.
private const val SESSION_CHANNEL = "temporary_full_charge"
private const val GESTURE_CHANNEL = "reconnect_gesture"
private const val RECOVERY_CHANNEL = "charge_policy_recovery"
private const val MONITOR_CHANNEL = "background_monitor"

fun ensureChannels(context: Context) {
val manager = context.getSystemService(NotificationManager::class.java)
// Covers the whole temporary-override lifecycle — lifting the limit and putting it back —
// because [recovering] shares it with [session]. Unlike importance, a channel's name and
// description do update on an existing channel, so widening the wording reaches installs
// that already have it.
manager.createNotificationChannel(
NotificationChannel(
SESSION_CHANNEL,
context.getString(R.string.session_channel_name),
NotificationManager.IMPORTANCE_LOW,
).apply {
description = "Shows while Amply temporarily allows charging to 100%"
description = context.getString(R.string.session_channel_description)
setShowBadge(false)
},
)
// Its own channel at DEFAULT importance so the persistent reconnect monitor is
// reliably visible in the status bar (the shared low channel was easy to miss).
// Its own channel, kept separate from the session so the user can silence one without the
// other, but LOW: the gesture notification is a passive standing cue that can sit there for
// hours, so it belongs in the shade's silent section. DEFAULT bought nothing anyway — all
// notifications here share SESSION_ID and set onlyAlertOnce, so the reconnect countdown
// never re-alerted; the importance only produced one ding when the service went foreground.
// LOW still keeps a status-bar icon, which is what makes an armed gesture discoverable.
// Importance is fixed once a channel exists, so this only takes effect on a fresh install —
// deliberately not migrated with a new channel id while the app is pre-launch.
manager.createNotificationChannel(
NotificationChannel(
GESTURE_CHANNEL,
context.getString(R.string.gesture_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
NotificationManager.IMPORTANCE_LOW,
).apply {
description = "Shows while the reconnect-for-100% gesture is watching for a replug"
description = context.getString(R.string.gesture_channel_description)
setShowBadge(false)
},
)
// HIGH, matching the charge alarm: this fires when the protective policy could NOT be
// restored, so the battery charges unprotected until the user intervenes — the exact
// failure the app exists to prevent, and one that otherwise goes unnoticed overnight. It
// would be backwards for a convenience reminder to out-rank it. Rare, auto-cancelling, and
// withdrawn the moment a restore succeeds, so the heads-up costs nothing when all is well.
manager.createNotificationChannel(
NotificationChannel(
RECOVERY_CHANNEL,
context.getString(R.string.recovery_channel_name),
NotificationManager.IMPORTANCE_DEFAULT,
),
NotificationManager.IMPORTANCE_HIGH,
).apply {
description = context.getString(R.string.recovery_channel_description)
},
)
// Quiet channel for the "alive only to observe battery" case (e.g. charge alarm). LOW so the
// background foreground-service notification doesn't draw attention like the gesture cue.
// Quiet channel for the "alive only to observe battery" case (e.g. charge alarm). Separate
// from the gesture channel despite sharing LOW, so silencing the watcher's presence entirely
// does not also hide the gesture that the user opted into.
manager.createNotificationChannel(
NotificationChannel(
MONITOR_CHANNEL,
Expand Down Expand Up @@ -191,6 +211,12 @@ object SessionNotifications {
return builder.build()
}

/**
* Progress while a restore converges on the charging hardware. Deliberately shares
* [SESSION_CHANNEL] with [session] rather than the alerting recovery channel: this is the
* passive end of the same override the user started, and the recovery channel is HIGH, which
* would make it heads-up on every boot that owes a restore.
*/
fun recovering(context: Context): Notification {
ensureChannels(context)
val openPendingIntent = PendingIntent.getActivity(
Expand Down Expand Up @@ -236,6 +262,9 @@ object SessionNotifications {
.setContentTitle(context.getString(R.string.recovery_notification_title))
.setContentText(context.getString(bodyRes))
.setContentIntent(openIntent)
// The only notification here reporting a failed state, so it is the only one
// that should rank and filter as an error rather than as service noise.
.setCategory(NotificationCompat.CATEGORY_ERROR)
.setAutoCancel(true)
.build(),
)
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
<string name="tile_active">Restore limit</string>
<string name="tile_override_active">Temporary override active</string>
<string name="widget_description">Amply charge controls</string>
<string name="session_channel_name">Temporary full charge</string>
<string name="session_channel_name">Full charge and restore</string>
<string name="session_channel_description" formatted="false">Shows while Amply is allowing a full charge, and while it restores your limit afterwards</string>
<string name="session_notification_title" formatted="false">Charging to 100% once</string>
<string name="session_notification_armed">Waiting for a charger</string>
<string name="session_notification_active">Charge protection returns at 100% or when unplugged.</string>
<string name="session_notification_restore">Restore now</string>
<string name="gesture_channel_name">Reconnect gesture</string>
<string name="gesture_channel_description" formatted="false">Shows while the reconnect-for-100% gesture is watching for a replug</string>
<string name="gesture_notification_title">Reconnect gesture is on</string>
<string name="gesture_notification_waiting">Waiting for a quick unplug and reconnect while your charge limit is holding.</string>
<string name="gesture_notification_waiting_limit">Waiting for a quick unplug and reconnect at your %1$d%% limit.</string>
Expand All @@ -20,6 +22,7 @@
<string name="gesture_notification_waiting_any_level">Waiting for a quick unplug and reconnect at any charge level.</string>
<string name="gesture_notification_disable_hint">You can turn this notification off (long-press it) without stopping the gesture.</string>
<string name="recovery_channel_name">Charge policy recovery</string>
<string name="recovery_channel_description">Alerts you when your protective charge limit could not be restored</string>
<string name="recovery_notification_title">Charge limit needs attention</string>
<string name="recovery_notification_body">Open Amply and restart Shizuku to restore your protective policy.</string>
<string name="recovery_notification_body_convergence">The protective policy was rewritten, but the charging hardware has not confirmed it yet. Open Amply to check.</string>
Expand Down
Loading