diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-06-22 09:53:33 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2020-06-22 09:53:33 -0300 |
| commit | 6a8d202fcef9fdfd32f873d25550d78bff3ffeb6 (patch) | |
| tree | e4b51f0056cfcc3b3522fd45bb772727671459f7 | |
| parent | 9f59403aaaad0c31120046d75acfa472f6b96ad2 (diff) | |
| parent | 33399bc388ee48b111624fa6c0154f9d00d99960 (diff) | |
| download | mullvadvpn-6a8d202fcef9fdfd32f873d25550d78bff3ffeb6.tar.xz mullvadvpn-6a8d202fcef9fdfd32f873d25550d78bff3ffeb6.zip | |
Merge branch 'time-running-out-notification'
6 files changed, 163 insertions, 17 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 22848b2491..e6c4bf2ab0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ Line wrap the file at 100 chars. Th ## [Unreleased] +### Added +#### Android +- Show a system notification when the account time will soon run out. ## [2020.5-beta2] - 2020-06-16 diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index 0998ddcc1c..795f5596df 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -8,15 +8,19 @@ import android.os.Binder import android.os.IBinder import android.util.Log import java.io.File +import kotlin.properties.Delegates.observable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.model.Settings +import net.mullvad.mullvadvpn.service.notifications.AccountExpiryNotification import net.mullvad.mullvadvpn.service.tunnelstate.TunnelStateUpdater import net.mullvad.mullvadvpn.ui.MainActivity import net.mullvad.talpid.TalpidVpnService import net.mullvad.talpid.util.EventNotifier +import net.mullvad.talpid.util.autoSubscribable +import org.joda.time.DateTime private const val RELAYS_FILE = "relays.json" @@ -41,13 +45,32 @@ class MullvadVpnService : TalpidVpnService() { private var startDaemonJob: Job? = null - private var instance: ServiceInstance? = null - set(value) { - if (field != value) { - field?.onDestroy() - field = value - serviceNotifier.notify(value) + private var instance by observable<ServiceInstance?>(null) { _, oldInstance, newInstance -> + if (newInstance != oldInstance) { + oldInstance?.onDestroy() + + accountExpiryNotification = newInstance?.daemon?.let { daemon -> + AccountExpiryNotification(this, daemon) } + + accountNumberEvents = newInstance?.accountCache?.onAccountNumberChange + accountExpiryEvents = newInstance?.accountCache?.onAccountExpiryChange + + serviceNotifier.notify(newInstance) + } + } + + private var accountNumberEvents by autoSubscribable<String?>(this, null) { accountNumber -> + loggedIn = accountNumber != null + } + + private var accountExpiryEvents by autoSubscribable<DateTime?>(this, null) { expiry -> + accountExpiryNotification?.accountExpiry = expiry + } + + private var accountExpiryNotification + by observable<AccountExpiryNotification?>(null) { _, oldNotification, _ -> + oldNotification?.accountExpiry = null } private lateinit var keyguardManager: KeyguardManager @@ -69,11 +92,9 @@ class MullvadVpnService : TalpidVpnService() { } } - private var isBound = false - set(value) { - field = value - notificationManager.lockedToForeground = value - } + private var isBound by observable(false) { _, _, isBound -> + notificationManager.lockedToForeground = isBound + } override fun onCreate() { super.onCreate() @@ -195,11 +216,7 @@ class MullvadVpnService : TalpidVpnService() { } private fun setUpInstance(daemon: MullvadDaemon, settings: Settings) { - val settingsListener = SettingsListener(daemon, settings).apply { - accountNumberNotifier.subscribe(this@MullvadVpnService) { accountNumber -> - loggedIn = accountNumber != null - } - } + val settingsListener = SettingsListener(daemon, settings) val connectionProxy = ConnectionProxy(this, daemon).apply { when (pendingAction) { diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt new file mode 100644 index 0000000000..34ac1d5a7d --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/AccountExpiryNotification.kt @@ -0,0 +1,100 @@ +package net.mullvad.mullvadvpn.service.notifications + +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.net.Uri +import kotlin.properties.Delegates.observable +import kotlinx.coroutines.delay +import net.mullvad.mullvadvpn.R +import net.mullvad.mullvadvpn.service.MullvadDaemon +import net.mullvad.mullvadvpn.util.JobTracker +import org.joda.time.DateTime +import org.joda.time.Duration + +class AccountExpiryNotification(val context: Context, val daemon: MullvadDaemon) { + companion object { + val NOTIFICATION_ID: Int = 2 + val REMAINING_TIME_FOR_REMINDERS = Duration.standardDays(2) + val TIME_BETWEEN_CHECKS: Long = 12 /* h */ * 60 /* min */ * 60 /* s */ * 1000 /* ms */ + } + + private val jobTracker = JobTracker() + private val resources = context.resources + + private val buyMoreTimeUrl = resources.getString(R.string.account_url) + + private val channel = NotificationChannel( + context, + "mullvad_account_time", + R.string.account_time_notification_channel_name, + R.string.account_time_notification_channel_description, + NotificationManager.IMPORTANCE_HIGH + ) + + var accountExpiry by observable<DateTime?>(null) { _, oldValue, newValue -> + if (oldValue != newValue) { + jobTracker.newUiJob("update") { update(newValue) } + } + } + + private suspend fun update(accountExpiry: DateTime?) { + val remainingTime = accountExpiry?.let { expiry -> Duration(DateTime.now(), expiry) } + + if (remainingTime != null && remainingTime.isShorterThan(REMAINING_TIME_FOR_REMINDERS)) { + val notification = build(accountExpiry, remainingTime) + + channel.notificationManager.notify(NOTIFICATION_ID, notification) + + jobTracker.newUiJob("scheduleUpdate") { scheduleUpdate() } + } else { + channel.notificationManager.cancel(NOTIFICATION_ID) + jobTracker.cancelJob("scheduleUpdate") + } + } + + private suspend fun scheduleUpdate() { + delay(TIME_BETWEEN_CHECKS) + update(accountExpiry) + } + + private suspend fun build(expiry: DateTime, remainingTime: Duration): Notification { + val url = jobTracker.runOnBackground { + Uri.parse("$buyMoreTimeUrl?token=${daemon.getWwwAuthToken()}") + } + + val intent = Intent(Intent.ACTION_VIEW, url) + val flags = PendingIntent.FLAG_UPDATE_CURRENT + val pendingIntent = PendingIntent.getActivity(context, 1, intent, flags) + + return channel.buildNotification(pendingIntent, format(expiry, remainingTime)) + } + + private fun format(expiry: DateTime, remainingTime: Duration): String { + if (remainingTime.isShorterThan(Duration.ZERO)) { + return resources.getString(R.string.account_credit_has_expired) + } else { + val remainingTimeInfo = remainingTime.toPeriodTo(expiry) + + if (remainingTimeInfo.days >= 1) { + return getRemainingText( + R.plurals.account_credit_expires_in_days, + remainingTime.standardDays.toInt() + ) + } else if (remainingTimeInfo.hours >= 1) { + return getRemainingText( + R.plurals.account_credit_expires_in_hours, + remainingTime.standardHours.toInt() + ) + } else { + return resources.getString(R.string.account_credit_expires_in_a_few_minutes) + } + } + } + + private fun getRemainingText(pluralId: Int, quantity: Int): String { + return resources.getQuantityString(pluralId, quantity, quantity) + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt index 2546c8955f..037a01033d 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/notifications/NotificationChannel.kt @@ -37,6 +37,10 @@ class NotificationChannel( } } + fun buildNotification(intent: PendingIntent, title: String): Notification { + return buildNotification(intent, title, emptyList()) + } + fun buildNotification(intent: PendingIntent, title: Int): Notification { return buildNotification(intent, title, emptyList()) } @@ -46,10 +50,18 @@ class NotificationChannel( title: Int, actions: List<NotificationCompat.Action> ): Notification { + return buildNotification(pendingIntent, context.getString(title), actions) + } + + fun buildNotification( + pendingIntent: PendingIntent, + title: String, + actions: List<NotificationCompat.Action> + ): Notification { val builder = NotificationCompat.Builder(context, id) .setSmallIcon(R.drawable.small_logo_black) .setColor(badgeColor) - .setContentTitle(context.getString(title)) + .setContentTitle(title) .setContentIntent(pendingIntent) for (action in actions) { diff --git a/android/src/main/res/values/plurals.xml b/android/src/main/res/values/plurals.xml index 679c4f46db..224cc9cb79 100644 --- a/android/src/main/res/values/plurals.xml +++ b/android/src/main/res/values/plurals.xml @@ -32,4 +32,12 @@ <item quantity="one">a year ago</item> <item quantity="other">%d years ago</item> </plurals> + <plurals name="account_credit_expires_in_days"> + <item quantity="one">Account credit expires in a day</item> + <item quantity="other">Account credit expires in %d days</item> + </plurals> + <plurals name="account_credit_expires_in_hours"> + <item quantity="one">Account credit expires in an hour</item> + <item quantity="other">Account credit expires in %d hours</item> + </plurals> </resources> diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml index 1bdf764ac4..48921c07a3 100644 --- a/android/src/main/res/values/strings.xml +++ b/android/src/main/res/values/strings.xml @@ -10,6 +10,9 @@ <string name="foreground_notification_channel_name">VPN tunnel status</string> <string name="foreground_notification_channel_description">Shows current VPN tunnel status</string> + <string name="account_time_notification_channel_name">Account time reminders</string> + <string name="account_time_notification_channel_description">Shows reminders when the account + time is about to expire</string> <string name="connecting_to_daemon">Connecting to Mullvad system service...</string> <string name="login_title">Login</string> <string name="login_description">Enter your account number</string> @@ -41,6 +44,9 @@ <string name="settings_account">Account</string> <string name="less_than_a_day_left">less than a day left</string> <string name="less_than_a_minute_ago">less than a minute ago</string> + <string name="account_credit_expires_in_a_few_minutes">Account credit expires in a few + minutes</string> + <string name="account_credit_has_expired">Account credit has expired</string> <string name="out_of_time">Out of time</string> <string name="no_more_vpn_time_left">You have no more VPN time left on this account. Either buy credit on our website or redeem a voucher.</string> |
