diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-06-24 11:23:48 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2019-06-24 11:23:48 -0300 |
| commit | 70779ba7aa7aa41a24558f99698d8b106a4ec457 (patch) | |
| tree | 9e7da4c3685bcd8568154aa25a19abdb21e7f249 /android/src/main/kotlin | |
| parent | fa066de0989e814532112bc1f521582dc5940063 (diff) | |
| parent | 67be4c180a615f2e89f394ec0f2ad2ee10d92a6a (diff) | |
| download | mullvadvpn-70779ba7aa7aa41a24558f99698d8b106a4ec457.tar.xz mullvadvpn-70779ba7aa7aa41a24558f99698d8b106a4ec457.zip | |
Merge branch 'problem-report-on-android'
Diffstat (limited to 'android/src/main/kotlin')
4 files changed, 294 insertions, 3 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt index 4b5414da71..1731ce2a56 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MainActivity.kt @@ -18,6 +18,7 @@ import android.support.v4.app.FragmentActivity import net.mullvad.mullvadvpn.dataproxy.AccountCache import net.mullvad.mullvadvpn.dataproxy.LocationInfoCache +import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport import net.mullvad.mullvadvpn.dataproxy.RelayListListener import net.mullvad.mullvadvpn.model.RelaySettings import net.mullvad.mullvadvpn.model.Settings @@ -36,6 +37,7 @@ class MainActivity : FragmentActivity() { val accountCache = AccountCache(this) val locationInfoCache = LocationInfoCache(asyncDaemon) + val problemReport = MullvadProblemReport() var relayListListener = RelayListListener(this) private var waitForDaemonJob: Job? = null diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ProblemReportFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ProblemReportFragment.kt new file mode 100644 index 0000000000..1e72aef5f6 --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ProblemReportFragment.kt @@ -0,0 +1,206 @@ +package net.mullvad.mullvadvpn + +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job + +import android.content.Context +import android.os.Bundle +import android.support.v4.app.Fragment +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import android.widget.EditText +import android.widget.TextView +import android.widget.ViewSwitcher + +import net.mullvad.mullvadvpn.dataproxy.MullvadProblemReport + +class ProblemReportFragment : Fragment() { + private lateinit var problemReport: MullvadProblemReport + + private lateinit var bodyContainer: ViewSwitcher + private lateinit var userEmailInput: EditText + private lateinit var userMessageInput: EditText + private lateinit var sendButton: Button + + private lateinit var sendingSpinner: View + private lateinit var sentSuccessfullyIcon: View + private lateinit var failedToSendIcon: View + + private lateinit var sendStatusLabel: TextView + private lateinit var sendDetailsLabel: TextView + private lateinit var responseMessageLabel: TextView + private lateinit var responseEmailLabel: TextView + + private lateinit var editMessageButton: Button + private lateinit var tryAgainButton: Button + + private var sendReportJob: Job? = null + + override fun onAttach(context: Context) { + super.onAttach(context) + + val parentActivity = context as MainActivity + + problemReport = parentActivity.problemReport + problemReport.collect() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val view = inflater.inflate(R.layout.problem_report, container, false) + + view.findViewById<View>(R.id.back).setOnClickListener { + activity?.onBackPressed() + } + + bodyContainer = view.findViewById<ViewSwitcher>(R.id.body_container) + userEmailInput = view.findViewById<EditText>(R.id.user_email) + userMessageInput = view.findViewById<EditText>(R.id.user_message) + sendButton = view.findViewById<Button>(R.id.send_button) + + sendingSpinner = view.findViewById<View>(R.id.sending_spinner) + sentSuccessfullyIcon = view.findViewById<View>(R.id.sent_successfully_icon) + failedToSendIcon = view.findViewById<View>(R.id.failed_to_send_icon) + + sendStatusLabel = view.findViewById<TextView>(R.id.send_status) + sendDetailsLabel = view.findViewById<TextView>(R.id.send_details) + responseMessageLabel = view.findViewById<TextView>(R.id.response_message) + responseEmailLabel = view.findViewById<TextView>(R.id.response_email) + + editMessageButton = view.findViewById<Button>(R.id.edit_message_button) + tryAgainButton = view.findViewById<Button>(R.id.try_again_button) + + sendButton.setOnClickListener { + sendReportJob?.cancel() + sendReportJob = sendReport() + } + + editMessageButton.setOnClickListener { + showForm() + } + + tryAgainButton.setOnClickListener { + sendReportJob = sendReport() + } + + userEmailInput.setText(problemReport.userEmail) + userMessageInput.setText(problemReport.userMessage) + + setSendButtonEnabled(!userMessageInput.text.isEmpty()) + userMessageInput.addTextChangedListener(InputWatcher()) + + return view + } + + override fun onDestroyView() { + sendReportJob?.cancel() + + problemReport.userEmail = userEmailInput.text.toString() + problemReport.userMessage = userMessageInput.text.toString() + problemReport.deleteReportFile() + + super.onDestroyView() + } + + private fun sendReport() = GlobalScope.launch(Dispatchers.Main) { + val userEmail = userEmailInput.text.toString() + + problemReport.userEmail = userEmail + problemReport.userMessage = userMessageInput.text.toString() + + showSendingScreen() + + if (problemReport.send().await()) { + clearForm() + showSuccessScreen(userEmail) + } else { + showErrorScreen() + } + } + + private fun clearForm() { + userEmailInput.setText("") + userMessageInput.setText("") + + problemReport.userEmail = "" + problemReport.userMessage = "" + } + + private fun showForm() { + bodyContainer.displayedChild = 0 + } + + private fun showSendingScreen() { + bodyContainer.displayedChild = 1 + + sendingSpinner.visibility = View.VISIBLE + sentSuccessfullyIcon.visibility = View.GONE + failedToSendIcon.visibility = View.GONE + + sendStatusLabel.visibility = View.VISIBLE + sendDetailsLabel.visibility = View.GONE + responseMessageLabel.visibility = View.GONE + responseEmailLabel.visibility = View.GONE + + sendStatusLabel.setText(R.string.sending) + + editMessageButton.visibility = View.GONE + tryAgainButton.visibility = View.GONE + } + + private fun showSuccessScreen(userEmail: String) { + sendingSpinner.visibility = View.GONE + + sentSuccessfullyIcon.visibility = View.VISIBLE + sendStatusLabel.visibility = View.VISIBLE + sendDetailsLabel.visibility = View.VISIBLE + + if (!userEmail.isEmpty()) { + responseMessageLabel.visibility = View.VISIBLE + responseEmailLabel.visibility = View.VISIBLE + responseEmailLabel.text = userEmail + } + + sendStatusLabel.setText(R.string.sent) + sendDetailsLabel.setText(R.string.sent_thanks) + } + + private fun showErrorScreen() { + sendingSpinner.visibility = View.GONE + + failedToSendIcon.visibility = View.VISIBLE + sendStatusLabel.visibility = View.VISIBLE + sendDetailsLabel.visibility = View.VISIBLE + + sendStatusLabel.setText(R.string.failed_to_send) + sendDetailsLabel.setText(R.string.failed_to_send_details) + + editMessageButton.visibility = View.VISIBLE + tryAgainButton.visibility = View.VISIBLE + } + + private fun setSendButtonEnabled(enabled: Boolean) { + sendButton.setEnabled(enabled) + sendButton.alpha = if (enabled) 1.0F else 0.5F + } + + inner class InputWatcher : TextWatcher { + override fun beforeTextChanged(text: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(text: CharSequence, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(text: Editable) { + setSendButtonEnabled(!text.isEmpty()) + } + } +} diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt index ee89ed7945..3400d43e58 100644 --- a/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/SettingsFragment.kt @@ -34,7 +34,12 @@ class SettingsFragment : Fragment() { activity?.finishAndRemoveTask() } - view.findViewById<View>(R.id.account).setOnClickListener { openAccountSettings() } + view.findViewById<View>(R.id.account).setOnClickListener { + openSubFragment(AccountFragment()) + } + view.findViewById<View>(R.id.report_a_problem).setOnClickListener { + openSubFragment(ProblemReportFragment()) + } remainingTimeLabel = RemainingTimeLabel(parentActivity, view) @@ -51,7 +56,7 @@ class SettingsFragment : Fragment() { super.onDestroyView() } - private fun openAccountSettings() { + private fun openSubFragment(fragment: Fragment) { fragmentManager?.beginTransaction()?.apply { setCustomAnimations( R.anim.fragment_enter_from_right, @@ -59,7 +64,7 @@ class SettingsFragment : Fragment() { R.anim.fragment_half_enter_from_left, R.anim.fragment_exit_to_right ) - replace(R.id.main_fragment, AccountFragment()) + replace(R.id.main_fragment, fragment) addToBackStack(null) commit() } diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt new file mode 100644 index 0000000000..d1794fbc3f --- /dev/null +++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt @@ -0,0 +1,78 @@ +package net.mullvad.mullvadvpn.dataproxy + +import java.io.File + +import kotlinx.coroutines.async +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope + +const val PROBLEM_REPORT_PATH = "/data/data/net.mullvad.mullvadvpn/problem_report.txt" + +class MullvadProblemReport { + private var collectJob: Deferred<Boolean>? = null + private var sendJob: Deferred<Boolean>? = null + + var userEmail = "" + var userMessage = "" + + val isActive: Boolean + get() { + synchronized(this) { + val collectJob = this.collectJob + val sendJob = this.sendJob + + return (collectJob != null && collectJob.isActive) || + (sendJob != null && sendJob.isActive) + } + } + + init { + System.loadLibrary("mullvad_jni") + } + + fun collect() { + synchronized(this) { + if (!isActive) { + collectJob = GlobalScope.async(Dispatchers.Default) { + deleteReportFile() + collectReport(PROBLEM_REPORT_PATH) + } + } + } + } + + fun send(): Deferred<Boolean> { + synchronized(this) { + var currentJob = sendJob + + if (currentJob == null || currentJob.isCompleted) { + currentJob = GlobalScope.async(Dispatchers.Default) { + val result = (collectJob?.await() ?: false) && + sendProblemReport(userEmail, userMessage, PROBLEM_REPORT_PATH) + + if (result) { + deleteReportFile() + } + + result + } + + sendJob = currentJob + } + + return currentJob + } + } + + fun deleteReportFile() { + File(PROBLEM_REPORT_PATH).delete() + } + + private external fun collectReport(reportPath: String): Boolean + private external fun sendProblemReport( + userEmail: String, + userMessage: String, + reportPath: String + ): Boolean +} |
