package org.pacien.tincapp.activities import android.app.Activity import android.app.ProgressDialog import android.content.Intent import android.net.VpnService import android.os.Bundle import android.support.v4.widget.SwipeRefreshLayout import android.support.v7.app.AlertDialog import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.TextView import kotlinx.android.synthetic.main.base.* import kotlinx.android.synthetic.main.dialog_decrypt_keys.view.* import kotlinx.android.synthetic.main.fragment_list_view.* import kotlinx.android.synthetic.main.fragment_network_list_header.* import org.pacien.tincapp.R import org.pacien.tincapp.context.AppPaths import org.pacien.tincapp.extensions.Android.setElements import org.pacien.tincapp.intent.Actions import org.pacien.tincapp.intent.BroadcastMapper import org.pacien.tincapp.service.TincVpnService import org.pacien.tincapp.utils.TincKeyring /** * @author pacien */ class StartActivity : BaseActivity() { companion object { private const val PERMISSION_REQUEST = 0 } private val networkList = object : AdapterView.OnItemClickListener, SwipeRefreshLayout.OnRefreshListener { private var networkListAdapter: ArrayAdapter? = null fun init() { networkListAdapter = ArrayAdapter(this@StartActivity, R.layout.fragment_list_item) layoutInflater.inflate(R.layout.fragment_list_view, main_content) list_wrapper.setOnRefreshListener(this) list.addHeaderView(layoutInflater.inflate(R.layout.fragment_network_list_header, list, false), null, false) list.addFooterView(View(this@StartActivity), null, false) list.adapter = networkListAdapter list.onItemClickListener = this } fun destroy() { networkListAdapter = null } override fun onRefresh() { val networks = AppPaths.confDir()?.list()?.toList() ?: emptyList() runOnUiThread { networkListAdapter?.setElements(networks) setPlaceholderVisibility() list_wrapper.isRefreshing = false } } override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { connectionStarter.tryStart(netName = (view as TextView).text.toString(), displayStatus = true) } private fun setPlaceholderVisibility() = if (networkListAdapter?.isEmpty != false) { network_list_placeholder.text = getListPlaceholderText() network_list_placeholder.visibility = View.VISIBLE } else { network_list_placeholder.visibility = View.GONE } private fun getListPlaceholderText() = if (!AppPaths.storageAvailable()) { getText(R.string.message_storage_unavailable) } else { getText(R.string.message_no_network_configuration_found) } } private val connectionStarter = object { private var netName: String? = null private var passphrase: String? = null private var displayStatus = false fun displayStatus() = displayStatus fun tryStart(netName: String? = null, passphrase: String? = null, displayStatus: Boolean? = null) { if (netName != null) this.netName = netName if (passphrase != null) this.passphrase = passphrase if (displayStatus != null) this.displayStatus = displayStatus val permissionRequestIntent = VpnService.prepare(this@StartActivity) if (permissionRequestIntent != null) return startActivityForResult(permissionRequestIntent, PERMISSION_REQUEST) if (TincKeyring.needsPassphrase(this.netName!!) && this.passphrase == null) return askForPassphrase() startVpn(this.netName!!, this.passphrase) } private fun askForPassphrase() { layoutInflater.inflate(R.layout.dialog_decrypt_keys, main_content, false).let { dialog -> AlertDialog.Builder(this@StartActivity) .setTitle(R.string.title_unlock_private_keys).setView(dialog) .setPositiveButton(R.string.action_unlock) { _, _ -> tryStart(passphrase = dialog.passphrase.text.toString()) } .setNegativeButton(R.string.action_cancel, { _, _ -> Unit }) .show() } } private fun startVpn(netName: String, passphrase: String? = null) { connectDialog = showProgressDialog(R.string.message_starting_vpn) TincVpnService.connect(netName, passphrase) } } private val broadcastMapper = BroadcastMapper(mapOf( Actions.EVENT_CONNECTED to this::onVpnStart, Actions.EVENT_ABORTED to this::onVpnStartError)) private var connectDialog: ProgressDialog? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) networkList.init() if (intent.action == Actions.ACTION_CONNECT && intent.data?.schemeSpecificPart != null) connectionStarter.tryStart(intent.data.schemeSpecificPart, intent.data.fragment, false) } override fun onCreateOptionsMenu(m: Menu): Boolean { menuInflater.inflate(R.menu.menu_start, m) return super.onCreateOptionsMenu(m) } override fun onDestroy() { networkList.destroy() connectDialog?.dismiss() super.onDestroy() } override fun onStart() { super.onStart() networkList.onRefresh() } override fun onResume() { super.onResume() if (TincVpnService.isConnected()) openStatusActivity(false) broadcastMapper.register() } override fun onPause() { broadcastMapper.unregister() super.onPause() } override fun onActivityResult(request: Int, result: Int, data: Intent?): Unit = when (request) { PERMISSION_REQUEST -> if (result == Activity.RESULT_OK) connectionStarter.tryStart() else Unit else -> throw IllegalArgumentException("Result for unknown request received.") } fun openConfigureActivity(@Suppress("UNUSED_PARAMETER") i: MenuItem) = startActivity(Intent(this, ConfigureActivity::class.java)) private fun onVpnStart() { connectDialog?.dismiss() if (connectionStarter.displayStatus()) openStatusActivity() finish() } private fun onVpnStartError() { connectDialog?.dismiss() } private fun openStatusActivity(transition: Boolean = true) = startActivity( Intent(this, StatusActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) .apply { if (!transition) addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) }) }