summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-07-31 13:24:03 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2019-07-31 13:24:03 -0300
commit1c7a7383d7bfbfe5eeeb44afeea6d615d1169dc7 (patch)
tree3445bf1d91b997ffe545a4b2be57ebf644975a9f
parent4bb7784e0bdac6ca641d8ab16e29e82aaec6fc21 (diff)
parenta4f8d869ab6948fbe0952ffcba62055bd9c18e54 (diff)
downloadmullvadvpn-1c7a7383d7bfbfe5eeeb44afeea6d615d1169dc7.tar.xz
mullvadvpn-1c7a7383d7bfbfe5eeeb44afeea6d615d1169dc7.zip
Merge branch 'show-app-updates'
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt64
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt13
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt94
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt23
-rw-r--r--android/src/main/res/drawable/icon_alert.xml12
-rw-r--r--android/src/main/res/drawable/icon_notification_warning.xml8
-rw-r--r--android/src/main/res/layout/settings.xml16
-rw-r--r--android/src/main/res/values/colors.xml1
-rw-r--r--android/src/main/res/values/strings.xml11
11 files changed, 229 insertions, 23 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
index 62a9765cce..e9713fff60 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ConnectFragment.kt
@@ -13,6 +13,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
+import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache
import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener
import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache
@@ -33,6 +34,7 @@ class ConnectFragment : Fragment() {
private lateinit var keyStatusListener: KeyStatusListener
private lateinit var locationInfoCache: LocationInfoCache
private lateinit var relayListListener: RelayListListener
+ private lateinit var versionInfoCache: AppVersionInfoCache
private lateinit var updateKeyStatusJob: Job
private lateinit var updateTunnelStateJob: Job
@@ -45,6 +47,7 @@ class ConnectFragment : Fragment() {
keyStatusListener = parentActivity.keyStatusListener
locationInfoCache = parentActivity.locationInfoCache
relayListListener = parentActivity.relayListListener
+ versionInfoCache = parentActivity.appVersionInfoCache
}
override fun onCreateView(
@@ -59,7 +62,7 @@ class ConnectFragment : Fragment() {
}
headerBar = HeaderBar(view, context!!)
- notificationBanner = NotificationBanner(view, context!!)
+ notificationBanner = NotificationBanner(view, context!!, versionInfoCache)
status = ConnectionStatus(view, context!!)
locationInfo = LocationInfo(view, locationInfoCache)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
index 33920b749a..2d129e3971 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt
@@ -19,6 +19,7 @@ import android.os.IBinder
import android.support.v4.app.FragmentActivity
import net.mullvad.mullvadvpn.dataproxy.AccountCache
+import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache
import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
import net.mullvad.mullvadvpn.dataproxy.KeyStatusListener
import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache
@@ -38,6 +39,7 @@ class MainActivity : FragmentActivity() {
var currentVersion = fetchCurrentVersion()
+ lateinit var appVersionInfoCache: AppVersionInfoCache
val connectionProxy = ConnectionProxy(this)
val keyStatusListener = KeyStatusListener(daemon)
val problemReport = MullvadProblemReport()
@@ -70,6 +72,8 @@ class MainActivity : FragmentActivity() {
if (savedInstanceState == null) {
addInitialFragment()
}
+
+ appVersionInfoCache = AppVersionInfoCache(this)
}
override fun onStart() {
@@ -102,6 +106,7 @@ class MainActivity : FragmentActivity() {
override fun onDestroy() {
accountCache.onDestroy()
+ appVersionInfoCache.onDestroy()
keyStatusListener.onDestroy()
relayListListener.onDestroy()
settingsListener.onDestroy()
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt
index 7060011158..992945ecef 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/NotificationBanner.kt
@@ -3,18 +3,26 @@ package net.mullvad.mullvadvpn
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.widget.ImageView
import android.widget.TextView
import android.view.View
+import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoCache
import net.mullvad.mullvadvpn.model.ActionAfterDisconnect
import net.mullvad.mullvadvpn.model.BlockReason
import net.mullvad.mullvadvpn.model.KeygenEvent
import net.mullvad.mullvadvpn.model.TunnelState
-class NotificationBanner(val parentView: View, val context: Context) {
+class NotificationBanner(
+ val parentView: View,
+ val context: Context,
+ val versionInfoCache: AppVersionInfoCache
+) {
private val accountUrl = Uri.parse(context.getString(R.string.account_url))
+ private val downloadUrl = Uri.parse(context.getString(R.string.download_url))
private val banner: View = parentView.findViewById(R.id.notification_banner)
+ private val status: ImageView = parentView.findViewById(R.id.notification_status)
private val title: TextView = parentView.findViewById(R.id.notification_title)
private val message: TextView = parentView.findViewById(R.id.notification_message)
private val icon: View = parentView.findViewById(R.id.notification_icon)
@@ -40,7 +48,7 @@ class NotificationBanner(val parentView: View, val context: Context) {
private fun update() {
externalLink = null
- updateBasedOnKeyState() || updateBasedOnTunnelState()
+ updateBasedOnKeyState() || updateBasedOnTunnelState() || updateBasedOnVersionInfo()
}
private fun updateBasedOnKeyState(): Boolean {
@@ -49,10 +57,10 @@ class NotificationBanner(val parentView: View, val context: Context) {
is KeygenEvent.NewKey -> return false
is KeygenEvent.TooManyKeys -> {
externalLink = accountUrl
- show(R.string.wireguard_error, R.string.too_many_keys)
+ showError(R.string.wireguard_error, R.string.too_many_keys)
}
is KeygenEvent.GenerationFailure -> {
- show(R.string.wireguard_error, R.string.failed_to_generate_key)
+ showError(R.string.wireguard_error, R.string.failed_to_generate_key)
}
}
@@ -65,20 +73,49 @@ class NotificationBanner(val parentView: View, val context: Context) {
when (state) {
is TunnelState.Disconnecting -> {
when (state.actionAfterDisconnect) {
- is ActionAfterDisconnect.Nothing -> hide()
+ is ActionAfterDisconnect.Nothing -> return false
is ActionAfterDisconnect.Block -> showBlocking(null)
is ActionAfterDisconnect.Reconnect -> showBlocking(null)
}
}
- is TunnelState.Disconnected -> hide()
+ is TunnelState.Disconnected -> return false
is TunnelState.Connecting -> showBlocking(null)
- is TunnelState.Connected -> hide()
+ is TunnelState.Connected -> return false
is TunnelState.Blocked -> showBlocking(state.reason)
}
return true
}
+ private fun updateBasedOnVersionInfo(): Boolean {
+ if (versionInfoCache.isLatest) {
+ hide()
+ } else {
+ val title: Int
+ val statusImage: Int
+ val template: Int
+
+ if (versionInfoCache.isSupported) {
+ title = R.string.update_available
+ template = R.string.update_available_description
+ statusImage = R.drawable.icon_notification_warning
+ } else {
+ title = R.string.unsupported_version
+ template = R.string.unsupported_version_description
+ statusImage = R.drawable.icon_notification_error
+ }
+
+ val parameter = versionInfoCache.upgradeVersion
+ val description = context.getString(template, parameter)
+
+ externalLink = downloadUrl
+
+ show(statusImage, title, description)
+ }
+
+ return true
+ }
+
private fun showBlocking(reason: BlockReason?) {
val messageText = when (reason) {
null -> null
@@ -92,10 +129,18 @@ class NotificationBanner(val parentView: View, val context: Context) {
is BlockReason.TapAdapterProblem -> R.string.tap_adapter_problem
}
- show(R.string.blocking_internet, messageText)
+ showError(R.string.blocking_internet, messageText)
+ }
+
+ private fun showError(titleText: Int, messageText: Int?) {
+ showError(titleText, messageText?.let { context.getString(it) })
+ }
+
+ private fun showError(titleText: Int, messageText: String?) {
+ show(R.drawable.icon_notification_error, titleText, messageText)
}
- private fun show(titleText: Int, messageText: Int?) {
+ private fun show(statusImage: Int, titleText: Int, messageText: String?) {
if (!visible) {
visible = true
banner.visibility = View.VISIBLE
@@ -103,6 +148,7 @@ class NotificationBanner(val parentView: View, val context: Context) {
banner.animate().translationY(0.0F).setDuration(350).start()
}
+ status.setImageResource(statusImage)
title.setText(titleText)
if (messageText == null) {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
index 967e91b6f9..f55426d811 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt
@@ -20,7 +20,9 @@ import android.widget.TextView
class SettingsFragment : Fragment() {
private lateinit var parentActivity: MainActivity
private lateinit var remainingTimeLabel: RemainingTimeLabel
+ private lateinit var appVersionWarning: View
private lateinit var appVersionLabel: TextView
+ private lateinit var appVersionFooter: View
private var showCurrentVersionJob: Job? = null
@@ -56,7 +58,9 @@ class SettingsFragment : Fragment() {
}
remainingTimeLabel = RemainingTimeLabel(parentActivity, view)
+ appVersionWarning = view.findViewById(R.id.app_version_warning)
appVersionLabel = view.findViewById<TextView>(R.id.app_version_label)
+ appVersionFooter = view.findViewById(R.id.app_version_footer)
showCurrentVersionJob = showCurrentVersion()
@@ -100,7 +104,16 @@ class SettingsFragment : Fragment() {
private fun showCurrentVersion() = GlobalScope.launch(Dispatchers.Main) {
val version = parentActivity.currentVersion.await()
+ val versionInfoCache = parentActivity.appVersionInfoCache
appVersionLabel.setText(version)
+
+ if (versionInfoCache.isLatest && versionInfoCache.isSupported) {
+ appVersionWarning.visibility = View.GONE
+ appVersionFooter.visibility = View.GONE
+ } else {
+ appVersionWarning.visibility = View.VISIBLE
+ appVersionFooter.visibility = View.VISIBLE
+ }
}
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt
new file mode 100644
index 0000000000..bcf0fef11e
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt
@@ -0,0 +1,94 @@
+package net.mullvad.mullvadvpn.dataproxy
+
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+
+import net.mullvad.mullvadvpn.MainActivity
+
+class AppVersionInfoCache(val parentActivity: MainActivity) {
+ companion object {
+ val KEY_CURRENT_IS_SUPPORTED = "current_is_supported"
+ val KEY_LAST_UPDATED = "last_updated"
+ val KEY_LATEST_STABLE = "latest_stable"
+ val KEY_LATEST = "latest"
+ val SHARED_PREFERENCES = "app_version_info_cache"
+ }
+
+ private val preferences =
+ parentActivity.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE)
+
+ private val updateVersionJob = updateVersion()
+
+ var version: String? = null
+ private set
+ var isStable = true
+ private set
+
+ var lastUpdated = 0L
+ private set
+ var isSupported = true
+ private set
+ var latestStable: String? = null
+ private set
+ var latest: String? = null
+ private set
+
+ var isLatest = true
+ private set
+ var upgradeVersion: String? = null
+ private set
+
+ private val listener = object : OnSharedPreferenceChangeListener {
+ override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) {
+ when (key) {
+ KEY_CURRENT_IS_SUPPORTED -> isSupported = preferences.getBoolean(key, isSupported)
+ KEY_LAST_UPDATED -> lastUpdated = preferences.getLong(key, lastUpdated)
+ KEY_LATEST_STABLE -> latestStable = preferences.getString(key, latestStable)
+ KEY_LATEST -> latest = preferences.getString(key, latest)
+ else -> return
+ }
+
+ updateUpgradeVersion()
+ }
+ }
+
+ init {
+ preferences.registerOnSharedPreferenceChangeListener(listener)
+
+ lastUpdated = preferences.getLong(KEY_LAST_UPDATED, 0L)
+ isSupported = preferences.getBoolean(KEY_CURRENT_IS_SUPPORTED, true)
+ latestStable = preferences.getString(KEY_LATEST_STABLE, null)
+ latest = preferences.getString(KEY_LATEST, null)
+ }
+
+ fun onDestroy() {
+ updateVersionJob.cancel()
+ preferences.unregisterOnSharedPreferenceChangeListener(listener)
+ }
+
+ private fun updateVersion() = GlobalScope.launch(Dispatchers.Default) {
+ val currentVersion = parentActivity.currentVersion.await()
+
+ version = currentVersion
+ isStable = !currentVersion.contains("-")
+
+ updateUpgradeVersion()
+ }
+
+ private fun updateUpgradeVersion() {
+ val target = if (isStable) latestStable else latest
+
+ if (target == version) {
+ isLatest = true
+ upgradeVersion = null
+ } else {
+ isLatest = false
+ upgradeVersion = target
+ }
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt
index 2b066c934b..26c6e2f885 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt
@@ -15,14 +15,9 @@ import net.mullvad.mullvadvpn.MullvadDaemon
val ONE_DAY_IN_MILLISECONDS = 24L * 60L * 60L * 1000L
val ONE_MINUTE_IN_MILLISECONDS = 60L * 1000L
-val KEY_CURRENT_IS_SUPPORTED = "current_is_supported"
-val KEY_LAST_UPDATED = "last_updated"
-val KEY_LATEST_STABLE = "latest_stable"
-val KEY_LATEST = "latest"
-val SHARED_PREFERENCES = "app_version_info_cache"
-
class AppVersionInfoFetcher(val daemon: Deferred<MullvadDaemon>, val context: Context) {
- private val preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE)
+ private val preferences =
+ context.getSharedPreferences(AppVersionInfoCache.SHARED_PREFERENCES, Context.MODE_PRIVATE)
private val mainLoop = run()
@@ -39,7 +34,7 @@ class AppVersionInfoFetcher(val daemon: Deferred<MullvadDaemon>, val context: Co
private fun calculateDelay(): Long {
val now = Calendar.getInstance().timeInMillis
- val lastUpdated = preferences.getLong(KEY_LAST_UPDATED, 0)
+ val lastUpdated = preferences.getLong(AppVersionInfoCache.KEY_LAST_UPDATED, 0)
val delta = now - lastUpdated
if (delta < 0 || delta >= ONE_DAY_IN_MILLISECONDS) {
@@ -63,11 +58,13 @@ class AppVersionInfoFetcher(val daemon: Deferred<MullvadDaemon>, val context: Co
if (versionInfo != null) {
preferences.edit().apply {
- putLong(KEY_LAST_UPDATED, now)
- putBoolean(KEY_CURRENT_IS_SUPPORTED, versionInfo.currentIsSupported)
- putString(KEY_LATEST_STABLE, versionInfo.latestStable)
- putString(KEY_LATEST, versionInfo.latest)
- commit()
+ with(AppVersionInfoCache) {
+ putLong(KEY_LAST_UPDATED, now)
+ putBoolean(KEY_CURRENT_IS_SUPPORTED, versionInfo.currentIsSupported)
+ putString(KEY_LATEST_STABLE, versionInfo.latestStable)
+ putString(KEY_LATEST, versionInfo.latest)
+ commit()
+ }
}
}
}
diff --git a/android/src/main/res/drawable/icon_alert.xml b/android/src/main/res/drawable/icon_alert.xml
new file mode 100644
index 0000000000..d07f49427b
--- /dev/null
+++ b/android/src/main/res/drawable/icon_alert.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ >
+ <path android:fillColor="#D0021B"
+ android:pathData="m12 24c-6.627417 0-12-5.372583-12-12s5.372583-12 12-12 12 5.372583 12 12-5.372583 12-12 12zm0-19.5c-.8284271 0-1.5.67157288-1.5 1.5v7.5c0 .8284271.6715729 1.5 1.5 1.5s1.5-.6715729 1.5-1.5v-7.5c0-.82842712-.6715729-1.5-1.5-1.5zm0 12c-.8284271 0-1.5.6715729-1.5 1.5s.6715729 1.5 1.5 1.5 1.5-.6715729 1.5-1.5-.6715729-1.5-1.5-1.5z"
+ />
+</vector>
diff --git a/android/src/main/res/drawable/icon_notification_warning.xml b/android/src/main/res/drawable/icon_notification_warning.xml
new file mode 100644
index 0000000000..f316b24154
--- /dev/null
+++ b/android/src/main/res/drawable/icon_notification_warning.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval"
+ >
+ <solid android:color="@color/yellow"/>
+ <size android:width="10dp" android:height="10dp"/>
+</shape>
diff --git a/android/src/main/res/layout/settings.xml b/android/src/main/res/layout/settings.xml
index fd86678a75..0037bec115 100644
--- a/android/src/main/res/layout/settings.xml
+++ b/android/src/main/res/layout/settings.xml
@@ -73,6 +73,13 @@
android:clickable="true"
android:gravity="center"
>
+ <ImageView android:id="@+id/app_version_warning"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="0"
+ android:paddingLeft="8dp"
+ android:src="@drawable/icon_alert"
+ />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -104,6 +111,15 @@
android:src="@drawable/icon_extlink"
/>
</LinearLayout>
+ <TextView android:id="@+id/app_version_footer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingHorizontal="24dp"
+ android:textColor="@color/white60"
+ android:textSize="13sp"
+ android:text="@string/update_available_footer"
+ />
<LinearLayout android:id="@+id/report_a_problem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/android/src/main/res/values/colors.xml b/android/src/main/res/values/colors.xml
index e882682904..44b8896a0a 100644
--- a/android/src/main/res/values/colors.xml
+++ b/android/src/main/res/values/colors.xml
@@ -17,6 +17,7 @@
<color name="red95">#C6021A</color>
<color name="red45">#73021B</color>
<color name="red40">#66021B</color>
+ <color name="yellow">#FFD323</color>
<color name="textInputBorder">#234161</color>
</resources>
diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml
index fbacb36a81..d419e4abc3 100644
--- a/android/src/main/res/values/strings.xml
+++ b/android/src/main/res/values/strings.xml
@@ -17,6 +17,7 @@
<string name="less_than_a_day_left">less than a day left</string>
<string name="out_of_time">Out of time</string>
<string name="app_version">App version</string>
+ <string name="update_available_footer">Update available, download to remain safe.</string>
<string name="report_a_problem">Report a problem</string>
<string name="quit">Quit</string>
@@ -74,6 +75,15 @@
<string name="wireguard_error">WireGuard error</string>
<string name="too_many_keys">Too many WireGuard keys registered to account</string>
<string name="failed_to_generate_key">Failed to generate WireGuard key</string>
+ <string name="update_available">Update available</string>
+ <string name="update_available_description">
+ Install Mullvad VPN (%1$s) to stay up to date
+ </string>
+ <string name="unsupported_version">Unsupported version</string>
+ <string name="unsupported_version_description">
+ You are running an unsupported app version. Please upgrade to %1$s now to ensure your
+ security
+ </string>
<string name="select_location">Select location</string>
<string name="select_location_description">
@@ -82,4 +92,5 @@
</string>
<string name="account_url">https://mullvad.net/en/account</string>
+ <string name="download_url">https://mullvad.net/en/download</string>
</resources>