MainFragment.kt 11.9 KB
Newer Older
Daniel Wolf's avatar
Daniel Wolf committed
1
2
3
package com.frostnerd.smokescreen.fragment

import android.app.Activity
4
import android.content.ActivityNotFoundException
Daniel Wolf's avatar
Daniel Wolf committed
5
6
import android.content.BroadcastReceiver
import android.content.Intent
7
import android.net.Uri
Daniel Wolf's avatar
Daniel Wolf committed
8
9
10
import android.net.VpnService
import android.os.Bundle
import android.view.LayoutInflater
11
import android.view.MotionEvent
Daniel Wolf's avatar
Daniel Wolf committed
12
13
import android.view.View
import android.view.ViewGroup
14
import android.widget.TextView
15
import android.widget.Toast
Daniel Wolf's avatar
Daniel Wolf committed
16
import androidx.appcompat.app.AppCompatActivity
Daniel Wolf's avatar
Daniel Wolf committed
17
import androidx.fragment.app.Fragment
Daniel Wolf's avatar
Daniel Wolf committed
18
import com.frostnerd.dnstunnelproxy.DnsServerInformation
Daniel Wolf's avatar
Daniel Wolf committed
19
import com.frostnerd.general.service.isServiceRunning
Daniel Wolf's avatar
Daniel Wolf committed
20
import com.frostnerd.lifecyclemanagement.LifecycleCoroutineScope
Daniel Wolf's avatar
Daniel Wolf committed
21
import com.frostnerd.lifecyclemanagement.launchWithLifecycle
22
import com.frostnerd.smokescreen.*
23
import com.frostnerd.smokescreen.activity.PinActivity
24
import com.frostnerd.smokescreen.activity.SpeedTestActivity
25
import com.frostnerd.smokescreen.dialog.ServerChoosalDialog
Daniel Wolf's avatar
Daniel Wolf committed
26
27
import com.frostnerd.smokescreen.service.Command
import com.frostnerd.smokescreen.service.DnsVpnService
Daniel Wolf's avatar
Daniel Wolf committed
28
import kotlinx.android.synthetic.main.fragment_main.*
29
30
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
Daniel Wolf's avatar
Daniel Wolf committed
31
import java.net.URL
Daniel Wolf's avatar
Daniel Wolf committed
32
33


Daniel Wolf's avatar
Daniel Wolf committed
34
35
/*
 * Copyright (C) 2019 Daniel Wolf (Ch4t4r)
Daniel Wolf's avatar
Daniel Wolf committed
36
 *
Daniel Wolf's avatar
Daniel Wolf committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You can contact the developer at daniel.wolf@frostnerd.com.
Daniel Wolf's avatar
Daniel Wolf committed
51
 */
52
53
class MainFragment : Fragment() {
    private val vpnRequestCode: Int = 1
54
    private var proxyState:ProxyState = ProxyState.NOT_RUNNING
55
    private var vpnStateReceiver: BroadcastReceiver? = null
Daniel Wolf's avatar
Daniel Wolf committed
56

57
58
59
60
61
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
Daniel Wolf's avatar
Daniel Wolf committed
62
63
64
65
66
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

    override fun onResume() {
        super.onResume()
67
68
69
70
        proxyState = if(requireContext().isServiceRunning(DnsVpnService::class.java)) {
            if(DnsVpnService.paused) ProxyState.PAUSED
            else ProxyState.RUNNING
        } else ProxyState.NOT_RUNNING
71
        updateVpnIndicators()
72
        context?.clearPreviousIptablesRedirect()
Daniel Wolf's avatar
Daniel Wolf committed
73
74
75
76
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        startButton.setOnClickListener {
77
78
            proxyState = when (proxyState) {
                ProxyState.RUNNING -> {
79
80
81
82
83
                    DnsVpnService.sendCommand(
                        requireContext(),
                        Command.STOP,
                        PinActivity.passPinExtras()
                    )
84
85
86
87
88
89
90
91
92
93
                    ProxyState.NOT_RUNNING
                }
                ProxyState.PAUSED -> {
                    DnsVpnService.sendCommand(it.context, Command.PAUSE_RESUME)
                    ProxyState.STARTING
                }
                else -> {
                    startVpn()
                    ProxyState.STARTING
                }
Daniel Wolf's avatar
Daniel Wolf committed
94
            }
95
            updateVpnIndicators()
Daniel Wolf's avatar
Daniel Wolf committed
96
        }
97
        startButton.setOnTouchListener { innerView , event ->
98
            if (proxyState == ProxyState.RUNNING || proxyState == ProxyState.STARTING) {
99
100
                false
            } else {
101
                var handled = false
102
103
                if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0) {
                    if (event.action == MotionEvent.ACTION_UP) {
104
                        if (!getPreferences().runWithoutVpn && VpnService.prepare(requireContext()) != null) {
105
                            showInfoTextDialog(requireContext(),
106
107
108
109
110
111
112
                                getString(R.string.dialog_overlaydetected_title),
                                getString(R.string.dialog_overlaydetected_message),
                                positiveButton = null,
                                negativeButton = null,
                                neutralButton = getString(android.R.string.ok) to { dialog, _ ->
                                    dialog.dismiss()
                                    startVpn()
113
                                    proxyState = ProxyState.STARTING
114
115
                                }
                            )
116
117
118
119
                            handled = true
                        }
                    }
                }
120
                if(event.action == MotionEvent.ACTION_UP && !handled) {
121
122
123
                    innerView.performClick()
                }
                true
124
125
            }
        }
126
        speedTest.setOnClickListener {
127
            startActivity(Intent(requireContext(), SpeedTestActivity::class.java))
128
        }
129
130
131
        vpnStateReceiver = requireContext().registerLocalReceiver(
            listOf(
                DnsVpnService.BROADCAST_VPN_ACTIVE,
132
133
134
                DnsVpnService.BROADCAST_VPN_INACTIVE,
                DnsVpnService.BROADCAST_VPN_PAUSED,
                DnsVpnService.BROADCAST_VPN_RESUMED
135
136
137
138
            )
        ) {
            if (it != null && it.action != null) {
                when (it.action) {
139
140
                    DnsVpnService.BROADCAST_VPN_ACTIVE, DnsVpnService.BROADCAST_VPN_RESUMED -> {
                        proxyState = ProxyState.RUNNING
Daniel Wolf's avatar
Daniel Wolf committed
141
142
                    }
                    DnsVpnService.BROADCAST_VPN_INACTIVE -> {
143
144
145
146
                        proxyState = ProxyState.NOT_RUNNING
                    }
                    DnsVpnService.BROADCAST_VPN_PAUSED -> {
                        proxyState = ProxyState.PAUSED
Daniel Wolf's avatar
Daniel Wolf committed
147
148
                    }
                }
149
                updateVpnIndicators()
Daniel Wolf's avatar
Daniel Wolf committed
150
151
152
            }
        }
        serverButton.setOnClickListener {
153
            ServerChoosalDialog(requireActivity() as AppCompatActivity) { config ->
Daniel Wolf's avatar
Daniel Wolf committed
154
                updatePrivacyPolicyLink(config)
155
                val prefs = requireContext().getPreferences()
156
                prefs.edit {
Daniel Wolf's avatar
Daniel Wolf committed
157
                    prefs.dnsServerConfig = config
158
                }
159
            }.show()
Daniel Wolf's avatar
Daniel Wolf committed
160
        }
161
        privacyStatementText.setOnClickListener {
162
            if (it.tag != null) {
163
164
165
                val i = Intent(Intent.ACTION_VIEW)
                val url = it.tag as URL
                i.data = Uri.parse(url.toURI().toString())
166
167
                try {
                    startActivity(i)
168
169
                } catch (e: ActivityNotFoundException) {
                    Toast.makeText(
170
                        requireContext(),
171
172
173
174
                        R.string.error_no_webbrowser_installed,
                        Toast.LENGTH_LONG
                    ).show()
                }
175
            }
176
        }
177
        GlobalScope.launch {
178
179
180
            val context = context
            if (isAdded && !isDetached && context != null) {
                val config = context.getPreferences().dnsServerConfig
181
                updatePrivacyPolicyLink(config)
182
183
            }
        }
184
        updateVpnIndicators()
Daniel Wolf's avatar
Daniel Wolf committed
185
186
187
188
    }

    override fun onDestroy() {
        super.onDestroy()
189
        if (vpnStateReceiver != null) requireContext().unregisterLocalReceiver(vpnStateReceiver!!)
Daniel Wolf's avatar
Daniel Wolf committed
190
191
    }

Daniel Wolf's avatar
Daniel Wolf committed
192
    private fun startVpn() {
193
        if(getPreferences().runWithoutVpn) {
Daniel Wolf's avatar
Daniel Wolf committed
194
195
            requireContext().startService(Intent(requireContext(), DnsVpnService::class.java))
        } else {
196
197
198
199
200
201
            val prepare = VpnService.prepare(requireContext()).apply {
                this?.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
            }

            if (prepare == null) {
                requireContext().startService(Intent(requireContext(), DnsVpnService::class.java))
202
                getPreferences().vpnInformationShown = true
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
            } else {
                if (getPreferences().vpnInformationShown) {
                    startActivityForResult(prepare, vpnRequestCode)
                } else {
                    showInfoTextDialog(requireContext(),
                        getString(R.string.dialog_vpninformation_title),
                        getString(R.string.dialog_vpninformation_message),
                        neutralButton = getString(android.R.string.ok) to { dialog, _ ->
                            startActivityForResult(prepare, vpnRequestCode)
                            dialog.dismiss()
                        }, withDialog = {
                            setCancelable(false)
                        })
                    getPreferences().vpnInformationShown = true
                }
218
            }
Daniel Wolf's avatar
Daniel Wolf committed
219
220
221
222
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
223
        if (requestCode == vpnRequestCode && resultCode == Activity.RESULT_OK) {
Daniel Wolf's avatar
Daniel Wolf committed
224
            startVpn()
225
        } else if (requestCode == vpnRequestCode) {
226
            updateVpnIndicators()
Daniel Wolf's avatar
Daniel Wolf committed
227
228
229
        }
    }

230
    private fun updateVpnIndicators() {
231
232
        val privateDnsActive = requireContext().isPrivateDnsActive
        var startButtonVisibility = View.VISIBLE
233
        var privacyTextVisibility = View.VISIBLE
234
235
        when(proxyState) {
            ProxyState.RUNNING -> {
236
                privateDnsInfo.visibility = View.INVISIBLE
Daniel Wolf's avatar
Daniel Wolf committed
237
238
239
240
                statusImage.setImageResource(R.drawable.ic_lock)
                statusImage.clearAnimation()
                startButton.setText(R.string.all_stop)
            }
241
            ProxyState.STARTING -> {
242
                privateDnsInfo.visibility = View.INVISIBLE
Daniel Wolf's avatar
Daniel Wolf committed
243
                startButton.setText(R.string.all_stop)
244
                statusImage.setImageResource(R.drawable.ic_lock_half_open)
Daniel Wolf's avatar
Daniel Wolf committed
245
            }
246
247
248
249
250
            ProxyState.PAUSED -> {
                privateDnsInfo.visibility = View.INVISIBLE
                startButton.setText(R.string.all_resume)
                statusImage.setImageResource(R.drawable.ic_lock_half_open)
            }
Daniel Wolf's avatar
Daniel Wolf committed
251
252
            else -> {
                startButton.setText(R.string.all_start)
253
                if (privateDnsActive) {
254
255
                    statusImage.setImageResource(R.drawable.ic_lock)
                    statusImage.clearAnimation()
256
                    privacyTextVisibility = View.INVISIBLE
257
                    startButtonVisibility = View.INVISIBLE
258
259
260
261
262
263
                    privateDnsInfo.visibility = View.VISIBLE
                } else {
                    statusImage.setImageResource(R.drawable.ic_lock_open)
                    statusImage.clearAnimation()
                    privateDnsInfo.visibility = View.INVISIBLE
                }
Daniel Wolf's avatar
Daniel Wolf committed
264
265
            }
        }
266
        startButton.visibility = startButtonVisibility
267
        privacyTextWrap.visibility = privacyTextVisibility
Daniel Wolf's avatar
Daniel Wolf committed
268
    }
269

270
    private fun updatePrivacyPolicyLink(serverInfo: DnsServerInformation<*>) {
Daniel Wolf's avatar
Daniel Wolf committed
271
272
273
274
        activity?.let { activity ->
            if (!serverInfo.specification.privacyPolicyURL.isNullOrBlank()) {
                LifecycleCoroutineScope(activity, ui = false).launch {
                    val url = URL(serverInfo.specification.privacyPolicyURL)
Daniel Wolf's avatar
Daniel Wolf committed
275
                    launchWithLifecycle(true) {
Daniel Wolf's avatar
Daniel Wolf committed
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
                        val text = view?.findViewById<TextView>(R.id.privacyStatementText)
                        text?.text =
                            getString(
                                R.string.main_dnssurveillance_privacystatement,
                                serverInfo.name
                            )
                        text?.tag = url
                        text?.visibility = View.VISIBLE
                    }
                }
            } else {
                LifecycleCoroutineScope(activity, ui = true).launch {
                    val text = view?.findViewById<TextView>(R.id.privacyStatementText)
                    text?.visibility = View.GONE
                }
291
            }
292
293
        }
    }
294
295
296
297

    enum class ProxyState {
        NOT_RUNNING, STARTING, RUNNING, PAUSED
    }
Daniel Wolf's avatar
Daniel Wolf committed
298
}