From 91adc78116f074f0a50bfdcc2069382677ea05cf Mon Sep 17 00:00:00 2001
From: pacien
Date: Tue, 22 Aug 2017 14:14:59 +0200
Subject: Better error handling
---
app/src/main/AndroidManifest.xml | 3 +-
.../org/pacien/tincapp/activities/BaseActivity.kt | 12 +--
.../pacien/tincapp/activities/ConfigureActivity.kt | 5 +-
.../pacien/tincapp/activities/LaunchActivity.kt | 62 ++++++++++++
.../pacien/tincapp/activities/PromptActivity.kt | 63 -------------
.../org/pacien/tincapp/activities/StartActivity.kt | 2 +-
.../main/java/org/pacien/tincapp/context/App.kt | 21 +++++
.../java/org/pacien/tincapp/context/AppPaths.kt | 3 +
.../java/org/pacien/tincapp/data/CidrAddress.kt | 8 +-
.../org/pacien/tincapp/intent/action/Actions.kt | 3 -
.../org/pacien/tincapp/service/TincVpnService.kt | 105 +++++++++++++--------
app/src/main/res/values/strings.xml | 7 ++
12 files changed, 176 insertions(+), 118 deletions(-)
create mode 100644 app/src/main/java/org/pacien/tincapp/activities/LaunchActivity.kt
delete mode 100644 app/src/main/java/org/pacien/tincapp/activities/PromptActivity.kt
(limited to 'app')
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5611e4c..dc406f3 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,6 +5,7 @@
+
diff --git a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
index 000320c..4904a66 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt
@@ -1,8 +1,6 @@
package org.pacien.tincapp.activities
import android.app.ProgressDialog
-import android.content.Intent
-import android.net.Uri
import android.os.Bundle
import android.support.annotation.StringRes
import android.support.design.widget.Snackbar
@@ -13,6 +11,7 @@ import android.view.MenuItem
import kotlinx.android.synthetic.main.base.*
import org.pacien.tincapp.BuildConfig
import org.pacien.tincapp.R
+import org.pacien.tincapp.context.App
import org.pacien.tincapp.context.AppInfo
/**
@@ -38,19 +37,16 @@ abstract class BaseActivity : AppCompatActivity() {
resources.getString(R.string.app_copyright) + " " +
resources.getString(R.string.app_license) + "\n\n" +
AppInfo.all())
- .setNeutralButton(R.string.action_open_project_website) { _, _ -> openWebsite(R.string.app_website_url) }
- .setPositiveButton(R.string.action_close, dismiss)
+ .setNeutralButton(R.string.action_open_project_website) { _, _ -> App.openURL(resources.getString(R.string.app_website_url)) }
+ .setPositiveButton(R.string.action_close, App.dismissAction)
.show()
}
- protected fun openWebsite(@StringRes url: Int) = startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(resources.getString(url))))
protected fun notify(@StringRes msg: Int) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show()
protected fun notify(msg: String) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show()
protected fun showProgressDialog(@StringRes msg: Int): ProgressDialog = ProgressDialog.show(this, null, getString(msg), true, false)
protected fun showErrorDialog(msg: String): AlertDialog = AlertDialog.Builder(this)
.setTitle(R.string.title_error).setMessage(msg)
- .setPositiveButton(R.string.action_close, dismiss).show()
-
- protected val dismiss = { _: Any, _: Any -> /* nop */ }
+ .setPositiveButton(R.string.action_close, App.dismissAction).show()
}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt
index b030a85..ea37944 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/ConfigureActivity.kt
@@ -13,6 +13,7 @@ import kotlinx.android.synthetic.main.page_configure.*
import org.pacien.tincapp.R
import org.pacien.tincapp.commands.Tinc
import org.pacien.tincapp.commands.TincApp
+import org.pacien.tincapp.context.App
import org.pacien.tincapp.context.AppPaths
import org.pacien.tincapp.extensions.Java.exceptionallyAccept
@@ -43,7 +44,7 @@ class ConfigureActivity : BaseActivity() {
AlertDialog.Builder(this).setTitle(R.string.title_new_network).setView(dialogFrame)
.setPositiveButton(R.string.action_create) { _, _ -> generateConf(netNameField.text.toString(), nodeNameField.text.toString()) }
- .setNegativeButton(R.string.action_cancel, dismiss).show()
+ .setNegativeButton(R.string.action_cancel, App.dismissAction).show()
}
fun openJoinNetworkDialog(@Suppress("UNUSED_PARAMETER") v: View) {
@@ -61,7 +62,7 @@ class ConfigureActivity : BaseActivity() {
AlertDialog.Builder(this).setTitle(R.string.title_join_network).setView(dialogFrame)
.setPositiveButton(R.string.action_join) { _, _ -> joinNetwork(netNameField.text.toString(), joinUrlField.text.toString()) }
- .setNegativeButton(R.string.action_cancel, dismiss).show()
+ .setNegativeButton(R.string.action_cancel, App.dismissAction).show()
}
private fun writeContent() {
diff --git a/app/src/main/java/org/pacien/tincapp/activities/LaunchActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/LaunchActivity.kt
new file mode 100644
index 0000000..6eb630d
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/activities/LaunchActivity.kt
@@ -0,0 +1,62 @@
+package org.pacien.tincapp.activities
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.net.VpnService
+import android.os.Bundle
+import org.pacien.tincapp.context.App
+import org.pacien.tincapp.intent.action.ACTION_CONNECT
+import org.pacien.tincapp.intent.action.ACTION_DISCONNECT
+import org.pacien.tincapp.intent.action.TINC_SCHEME
+import org.pacien.tincapp.service.TincVpnService
+
+/**
+ * @author pacien
+ */
+class LaunchActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ when (intent.action) {
+ ACTION_CONNECT -> requestPerm()
+ ACTION_DISCONNECT -> disconnect()
+ }
+ }
+
+ override fun onActivityResult(request: Int, result: Int, data: Intent?) {
+ if (result == Activity.RESULT_OK) TincVpnService.startVpn(intent.data.schemeSpecificPart)
+ finish()
+ }
+
+ private fun requestPerm() = VpnService.prepare(this).let {
+ if (it != null)
+ startActivityForResult(it, 0)
+ else
+ onActivityResult(0, Activity.RESULT_OK, null)
+ }
+
+ private fun disconnect() {
+ TincVpnService.stopVpn()
+ finish()
+ }
+
+ companion object {
+
+ fun connect(netName: String) {
+ App.getContext().startActivity(Intent(App.getContext(), LaunchActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setAction(ACTION_CONNECT)
+ .setData(Uri.Builder().scheme(TINC_SCHEME).opaquePart(netName).build()))
+ }
+
+ fun disconnect() {
+ App.getContext().startActivity(Intent(App.getContext(), LaunchActivity::class.java)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setAction(ACTION_DISCONNECT))
+ }
+
+ }
+
+}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/PromptActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/PromptActivity.kt
deleted file mode 100644
index 6310d63..0000000
--- a/app/src/main/java/org/pacien/tincapp/activities/PromptActivity.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.pacien.tincapp.activities
-
-import android.app.Activity
-import android.content.Intent
-import android.net.Uri
-import android.net.VpnService
-import android.os.Bundle
-import org.pacien.tincapp.BuildConfig
-import org.pacien.tincapp.context.App
-import org.pacien.tincapp.intent.action.ACTION_CONNECT
-import org.pacien.tincapp.intent.action.ACTION_DISCONNECT
-import org.pacien.tincapp.intent.action.TINC_SCHEME
-import org.pacien.tincapp.service.TincVpnService
-
-/**
- * @author pacien
- */
-class PromptActivity : Activity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- when (intent.action) {
- ACTION_CONNECT -> connect()
- ACTION_DISCONNECT -> disconnect()
- }
- }
-
- override fun onActivityResult(request: Int, result: Int, data: Intent?) {
- if (result == Activity.RESULT_OK) TincVpnService.startVpn(intent.data.schemeSpecificPart)
- finish()
- }
-
- private fun connect() = VpnService.prepare(this).let {
- if (it != null)
- startActivityForResult(it, 0)
- else
- onActivityResult(0, Activity.RESULT_OK, null)
- }
-
- private fun disconnect() {
- TincVpnService.stopVpn()
- finish()
- }
-
- companion object {
-
- fun connect(netName: String) {
- App.getContext().startActivity(Intent(App.getContext(), PromptActivity::class.java)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .setAction(ACTION_CONNECT)
- .setData(Uri.Builder().scheme(TINC_SCHEME).opaquePart(netName).build()))
- }
-
- fun disconnect() {
- App.getContext().startActivity(Intent(App.getContext(), PromptActivity::class.java)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .setAction(ACTION_DISCONNECT))
- }
-
- }
-
-}
diff --git a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
index e49e261..6ef8974 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
@@ -77,7 +77,7 @@ class StartActivity : BaseActivity(), AdapterView.OnItemClickListener, SwipeRefr
}
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) =
- PromptActivity.connect((view as TextView).text.toString())
+ LaunchActivity.connect((view as TextView).text.toString())
fun openConfigureActivity(@Suppress("UNUSED_PARAMETER") i: MenuItem) =
startActivity(Intent(this, ConfigureActivity::class.java))
diff --git a/app/src/main/java/org/pacien/tincapp/context/App.kt b/app/src/main/java/org/pacien/tincapp/context/App.kt
index 4b7e44e..7aaa3d0 100644
--- a/app/src/main/java/org/pacien/tincapp/context/App.kt
+++ b/app/src/main/java/org/pacien/tincapp/context/App.kt
@@ -2,6 +2,12 @@ package org.pacien.tincapp.context
import android.app.Application
import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.support.annotation.StringRes
+import android.support.v7.app.AlertDialog
+import android.view.WindowManager
+import org.pacien.tincapp.R
/**
* @author pacien
@@ -14,9 +20,24 @@ class App : Application() {
}
companion object {
+
private var appContext: Context? = null
+
fun getContext() = appContext!!
fun getResources() = getContext().resources!!
+
+ fun alert(@StringRes title: Int, msg: String, manualLink: String? = null) =
+ AlertDialog.Builder(getContext(), R.style.Theme_AppCompat_Dialog)
+ .setTitle(title).setMessage(msg)
+ .apply { if (manualLink != null) setNeutralButton(R.string.action_open_manual) { _, _ -> openURL(manualLink) } }
+ .setPositiveButton(R.string.action_close, dismissAction)
+ .create().apply { window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR) }.show()
+
+ fun openURL(url: String) =
+ appContext?.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+
+ val dismissAction = { _: Any, _: Any -> /* nop */ }
+
}
}
diff --git a/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt b/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt
index c745d4d..673faa7 100644
--- a/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt
+++ b/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt
@@ -1,6 +1,7 @@
package org.pacien.tincapp.context
import java.io.File
+import java.io.FileNotFoundException
/**
* @author pacien
@@ -30,6 +31,8 @@ object AppPaths {
fun logFile(netName: String) = File(cacheDir(), String.format(LOGFILE_FORMAT, netName))
fun pidFile(netName: String) = File(App.getContext().cacheDir, String.format(PIDFILE_FORMAT, netName))
+ fun existing(f: File) = f.apply { if (!exists()) throw FileNotFoundException(f.absolutePath) }
+
fun tincd() = File(binDir(), TINCD_BIN)
fun tinc() = File(binDir(), TINC_BIN)
diff --git a/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt b/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt
index bce9894..273b5a2 100644
--- a/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt
+++ b/app/src/main/java/org/pacien/tincapp/data/CidrAddress.kt
@@ -1,5 +1,7 @@
package org.pacien.tincapp.data
+import org.apache.commons.configuration2.ex.ConversionException
+
/**
* @author pacien
*/
@@ -9,7 +11,11 @@ data class CidrAddress(val address: String, val prefix: Int) {
private val SEPARATOR = "/"
- fun fromSlashSeparated(s: String) = CidrAddress(s.substringBefore(SEPARATOR), Integer.parseInt(s.substringAfter(SEPARATOR)))
+ fun fromSlashSeparated(s: String) = try {
+ CidrAddress(s.substringBefore(SEPARATOR), Integer.parseInt(s.substringAfter(SEPARATOR)))
+ } catch (e: Exception) {
+ throw ConversionException(e.message, e)
+ }
}
diff --git a/app/src/main/java/org/pacien/tincapp/intent/action/Actions.kt b/app/src/main/java/org/pacien/tincapp/intent/action/Actions.kt
index b210e14..ece9b68 100644
--- a/app/src/main/java/org/pacien/tincapp/intent/action/Actions.kt
+++ b/app/src/main/java/org/pacien/tincapp/intent/action/Actions.kt
@@ -11,7 +11,4 @@ private val PREFIX = "${BuildConfig.APPLICATION_ID}.intent.action"
val ACTION_CONNECT = "$PREFIX.CONNECT"
val ACTION_DISCONNECT = "$PREFIX.DISCONNECT"
-val ACTION_START_SERVICE = "$PREFIX.START_SERVICE"
-val ACTION_STOP_SERVICE = "$PREFIX.STOP_SERVICE"
-
val TINC_SCHEME = "tinc"
diff --git a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
index 12ac17f..cd1dd74 100644
--- a/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
+++ b/app/src/main/java/org/pacien/tincapp/service/TincVpnService.kt
@@ -5,7 +5,10 @@ import android.content.Intent
import android.net.Uri
import android.net.VpnService
import android.os.ParcelFileDescriptor
+import android.util.Log
+import org.apache.commons.configuration2.ex.ConversionException
import org.pacien.tincapp.BuildConfig
+import org.pacien.tincapp.R
import org.pacien.tincapp.commands.Tinc
import org.pacien.tincapp.commands.Tincd
import org.pacien.tincapp.context.App
@@ -13,9 +16,8 @@ import org.pacien.tincapp.context.AppPaths
import org.pacien.tincapp.data.VpnInterfaceConfiguration
import org.pacien.tincapp.extensions.Java.applyIgnoringException
import org.pacien.tincapp.extensions.VpnServiceBuilder.applyCfg
-import org.pacien.tincapp.intent.action.ACTION_START_SERVICE
-import org.pacien.tincapp.intent.action.ACTION_STOP_SERVICE
import org.pacien.tincapp.intent.action.TINC_SCHEME
+import java.io.FileNotFoundException
import java.io.IOException
/**
@@ -23,65 +25,90 @@ import java.io.IOException
*/
class TincVpnService : VpnService() {
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
- when (intent.action) {
- ACTION_START_SERVICE -> startVpn(intent.data.schemeSpecificPart)
- ACTION_STOP_SERVICE -> onDestroy()
- }
-
- return Service.START_STICKY
+ override fun onDestroy() {
+ stopVpn()
+ super.onDestroy()
}
- override fun onDestroy() {
- connected = false
-
- try {
- if (netName != null) Tinc.stop(netName!!)
- fd?.close()
- } catch (e: IOException) {
- e.printStackTrace()
- } finally {
- netName = null
- interfaceCfg = null
- fd = null
- super.onDestroy()
- }
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ if (isConnected()) stopVpn()
+ startVpn(intent.data.schemeSpecificPart)
+ return Service.START_REDELIVER_INTENT
}
private fun startVpn(netName: String) {
- if (isConnected()) onDestroy()
- TincVpnService.netName = netName
- TincVpnService.interfaceCfg = VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.netConfFile(netName))
-
- val net = Builder().setSession(netName).applyCfg(TincVpnService.interfaceCfg!!)
- applyIgnoringException(net::addDisallowedApplication, BuildConfig.APPLICATION_ID)
-
- try {
- fd = net.establish()
- Tincd.start(netName, fd!!.fd)
- } catch (e: IOException) {
- e.printStackTrace()
+ if (netName.isBlank())
+ return reportError(resources.getString(R.string.message_no_network_name_provided), docTopic = "intent-api")
+
+ if (!AppPaths.confDir(netName).exists())
+ return reportError(resources.getString(R.string.message_no_configuration_for_network_format, netName), docTopic = "configuration")
+
+ Log.i(TAG, "Starting tinc daemon for network \"$netName\".")
+
+ val interfaceCfg = try {
+ VpnInterfaceConfiguration.fromIfaceConfiguration(AppPaths.existing(AppPaths.netConfFile(netName)))
+ } catch (e: FileNotFoundException) {
+ return reportError(resources.getString(R.string.message_network_config_not_found_format, e.message!!), e, "configuration")
+ } catch (e: ConversionException) {
+ return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface")
}
- connected = true
+ val fd = try {
+ Builder().setSession(netName)
+ .applyCfg(interfaceCfg)
+ .also { applyIgnoringException(it::addDisallowedApplication, BuildConfig.APPLICATION_ID) }
+ .establish()
+ } catch (e: IllegalArgumentException) {
+ return reportError(resources.getString(R.string.message_network_config_invalid_format, e.message!!), e, "network-interface")
+ }
+
+ Tincd.start(netName, fd!!.fd)
+ setState(true, netName, interfaceCfg, fd)
+ Log.i(TAG, "tinc daemon started.")
+ }
+
+ private fun reportError(msg: String, e: Throwable? = null, docTopic: String? = null) {
+ if (e != null)
+ Log.e(TAG, msg, e)
+ else
+ Log.e(TAG, msg)
+
+ App.alert(R.string.title_unable_to_start_tinc, msg,
+ if (docTopic != null) resources.getString(R.string.app_doc_url_format, docTopic) else null)
}
companion object {
+ val TAG = this::class.java.canonicalName!!
+
private var connected: Boolean = false
private var netName: String? = null
private var interfaceCfg: VpnInterfaceConfiguration? = null
private var fd: ParcelFileDescriptor? = null
+ private fun setState(connected: Boolean, netName: String?, interfaceCfg: VpnInterfaceConfiguration?, fd: ParcelFileDescriptor?) {
+ TincVpnService.connected = connected
+ TincVpnService.netName = netName
+ TincVpnService.interfaceCfg = interfaceCfg
+ TincVpnService.fd = fd
+ }
+
fun startVpn(netName: String) {
App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java)
- .setAction(ACTION_START_SERVICE)
.setData(Uri.Builder().scheme(TINC_SCHEME).opaquePart(netName).build()))
}
fun stopVpn() {
- App.getContext().startService(Intent(App.getContext(), TincVpnService::class.java)
- .setAction(ACTION_STOP_SERVICE))
+ try {
+ Log.i(TAG, "Stopping any running tinc daemon.")
+ if (netName != null) Tinc.stop(netName!!)
+ fd?.close()
+ Log.i(TAG, "All tinc daemons stopped.")
+ } catch (e: IOException) {
+ Log.wtf(TAG, e)
+ } finally {
+ setState(false, null, null, null)
+ }
}
fun getCurrentNetName() = netName
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 85b5172..70a13da 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -5,6 +5,7 @@
Copyright 2017 Pacien.
Software distributed under the terms of the GNU General Public License v3.
http://tincapp.pacien.org
+ http://tincapp.pacien.org/doc.html#%1$s
App version %1$s (%2$s build)
Running on Android %1$s %2$s
@@ -39,6 +40,7 @@
Error
New network
Join network
+ Unable to start tinc
Close
Cancel
@@ -47,12 +49,17 @@
Join network via invitation URL
Create
Join
+ Open manual
No network configuration has been found.
No known node
Generating node configuration…
Joining network…
Network configuration successfully created.
+ No network name has been provided.
+ No configuration has been found for network \"%1$s\".
+ Network configuration file not found at \"%1$s\".
+ Invalid network configuration:\n\n%1$s
none
yes
--
cgit v1.2.3