aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt')
-rw-r--r--app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt201
1 files changed, 201 insertions, 0 deletions
diff --git a/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
new file mode 100644
index 0000000..50e002f
--- /dev/null
+++ b/app/src/main/java/org/pacien/tincapp/activities/StartActivity.kt
@@ -0,0 +1,201 @@
1/*
2 * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
3 * Copyright (C) 2017-2018 Pacien TRAN-GIRARD
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19package org.pacien.tincapp.activities
20
21import android.app.Activity
22import android.content.Intent
23import android.net.VpnService
24import android.os.Bundle
25import android.support.v4.widget.SwipeRefreshLayout
26import android.support.v7.app.AlertDialog
27import android.view.Menu
28import android.view.MenuItem
29import android.view.View
30import android.widget.AdapterView
31import android.widget.ArrayAdapter
32import android.widget.TextView
33import kotlinx.android.synthetic.main.base.*
34import kotlinx.android.synthetic.main.dialog_decrypt_keys.view.*
35import kotlinx.android.synthetic.main.fragment_list_view.*
36import kotlinx.android.synthetic.main.fragment_network_list_header.*
37import org.pacien.tincapp.R
38import org.pacien.tincapp.context.AppPaths
39import org.pacien.tincapp.extensions.Android.setElements
40import org.pacien.tincapp.intent.Actions
41import org.pacien.tincapp.intent.BroadcastMapper
42import org.pacien.tincapp.service.TincVpnService
43import org.pacien.tincapp.utils.TincKeyring
44
45/**
46 * @author pacien
47 */
48class StartActivity : BaseActivity() {
49 companion object {
50 private const val PERMISSION_REQUEST = 0
51 }
52
53 private val networkList = object : AdapterView.OnItemClickListener, SwipeRefreshLayout.OnRefreshListener {
54 private var networkListAdapter: ArrayAdapter<String>? = null
55
56 fun init() {
57 networkListAdapter = ArrayAdapter(this@StartActivity, R.layout.fragment_list_item)
58 layoutInflater.inflate(R.layout.fragment_list_view, main_content)
59 list_wrapper.setOnRefreshListener(this)
60 list.addHeaderView(layoutInflater.inflate(R.layout.fragment_network_list_header, list, false), null, false)
61 list.addFooterView(View(this@StartActivity), null, false)
62 list.adapter = networkListAdapter
63 list.onItemClickListener = this
64 }
65
66 fun destroy() {
67 networkListAdapter = null
68 }
69
70 override fun onRefresh() {
71 val networks = AppPaths.confDir().list()?.sorted() ?: emptyList()
72 runOnUiThread {
73 networkListAdapter?.setElements(networks)
74 setPlaceholderVisibility()
75 list_wrapper.isRefreshing = false
76 }
77 }
78
79 override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
80 connectionStarter.tryStart(netName = (view as TextView).text.toString(), displayStatus = true)
81 }
82
83 private fun setPlaceholderVisibility() = if (networkListAdapter?.isEmpty != false) {
84 network_list_placeholder.text = getListPlaceholderText()
85 network_list_placeholder.visibility = View.VISIBLE
86 } else {
87 network_list_placeholder.visibility = View.GONE
88 }
89
90 private fun getListPlaceholderText() = if (!AppPaths.storageAvailable()) {
91 getText(R.string.message_storage_unavailable)
92 } else {
93 getText(R.string.message_no_network_configuration_found)
94 }
95 }
96
97 private val connectionStarter = object {
98 private var netName: String? = null
99 private var passphrase: String? = null
100 private var displayStatus = false
101
102 fun displayStatus() = displayStatus
103
104 fun tryStart(netName: String? = null, passphrase: String? = null, displayStatus: Boolean? = null) {
105 if (netName != null) this.netName = netName
106 this.passphrase = passphrase
107 if (displayStatus != null) this.displayStatus = displayStatus
108
109 val permissionRequestIntent = VpnService.prepare(this@StartActivity)
110 if (permissionRequestIntent != null)
111 return startActivityForResult(permissionRequestIntent, PERMISSION_REQUEST)
112
113 if (TincKeyring.needsPassphrase(this.netName!!) && this.passphrase == null)
114 return askForPassphrase()
115
116 startVpn(this.netName!!, this.passphrase)
117 }
118
119 private fun askForPassphrase() {
120 layoutInflater.inflate(R.layout.dialog_decrypt_keys, main_content, false).let { dialog ->
121 AlertDialog.Builder(this@StartActivity)
122 .setTitle(R.string.title_unlock_private_keys).setView(dialog)
123 .setPositiveButton(R.string.action_unlock) { _, _ -> tryStart(passphrase = dialog.passphrase.text.toString()) }
124 .setNegativeButton(R.string.action_cancel) { _, _ -> Unit }
125 .show()
126 }
127 }
128
129 private fun startVpn(netName: String, passphrase: String? = null) {
130 connectDialog = showProgressDialog(R.string.message_starting_vpn)
131 TincVpnService.connect(netName, passphrase)
132 }
133 }
134
135 private val broadcastMapper = BroadcastMapper(mapOf(
136 Actions.EVENT_CONNECTED to this::onVpnStart,
137 Actions.EVENT_ABORTED to this::onVpnStartError))
138
139 private var connectDialog: AlertDialog? = null
140
141 override fun onCreate(savedInstanceState: Bundle?) {
142 super.onCreate(savedInstanceState)
143 networkList.init()
144
145 if (intent.action == Actions.ACTION_CONNECT && intent.data?.schemeSpecificPart != null)
146 connectionStarter.tryStart(intent.data.schemeSpecificPart, intent.data.fragment, false)
147 }
148
149 override fun onCreateOptionsMenu(m: Menu): Boolean {
150 menuInflater.inflate(R.menu.menu_start, m)
151 return super.onCreateOptionsMenu(m)
152 }
153
154 override fun onDestroy() {
155 networkList.destroy()
156 connectDialog?.dismiss()
157 super.onDestroy()
158 }
159
160 override fun onStart() {
161 super.onStart()
162 networkList.onRefresh()
163 }
164
165 override fun onResume() {
166 super.onResume()
167 if (TincVpnService.isConnected()) openStatusActivity(false)
168 broadcastMapper.register()
169 handleRecentCrash()
170 }
171
172 override fun onPause() {
173 broadcastMapper.unregister()
174 super.onPause()
175 }
176
177 override fun onActivityResult(request: Int, result: Int, data: Intent?): Unit = when (request) {
178 PERMISSION_REQUEST -> if (result == Activity.RESULT_OK) connectionStarter.tryStart() else Unit
179 else -> throw IllegalArgumentException("Result for unknown request received.")
180 }
181
182 fun openConfigureActivity(@Suppress("UNUSED_PARAMETER") i: MenuItem) =
183 startActivity(Intent(this, ConfigureActivity::class.java))
184
185 private fun onVpnStart() {
186 connectDialog?.dismiss()
187 if (connectionStarter.displayStatus()) openStatusActivity()
188 finish()
189 }
190
191 private fun onVpnStartError() {
192 connectDialog?.dismiss()
193 }
194
195 private fun openStatusActivity(transition: Boolean = true) =
196 startActivity(
197 Intent(this, StatusActivity::class.java)
198 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
199 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
200 .apply { if (!transition) addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) })
201}