diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-07-31 13:24:03 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-07-31 13:24:03 -0300 |
| commit | 1c7a7383d7bfbfe5eeeb44afeea6d615d1169dc7 (patch) | |
| tree | 3445bf1d91b997ffe545a4b2be57ebf644975a9f /android | |
| parent | 4bb7784e0bdac6ca641d8ab16e29e82aaec6fc21 (diff) | |
| parent | a4f8d869ab6948fbe0952ffcba62055bd9c18e54 (diff) | |
| download | mullvadvpn-1c7a7383d7bfbfe5eeeb44afeea6d615d1169dc7.tar.xz mullvadvpn-1c7a7383d7bfbfe5eeeb44afeea6d615d1169dc7.zip | |
Merge branch 'show-app-updates'
Diffstat (limited to 'android')
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> |
