package com.frostnerd.smokescreen
import android.content.Context
import android.content.DialogInterface
import android.content.pm.PackageManager
import android.graphics.Color
import android.text.Html
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.util.Linkify
import android.util.AndroidRuntimeException
import android.view.LayoutInflater
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.appcompat.app.AlertDialog
import com.frostnerd.dnstunnelproxy.KnownDnsServers
import com.frostnerd.encrypteddnstunnelproxy.AbstractHttpsDNSHandle
import com.frostnerd.encrypteddnstunnelproxy.HttpsDnsServerInformation
import com.frostnerd.encrypteddnstunnelproxy.QuicEngine
import com.frostnerd.encrypteddnstunnelproxy.quic.AbstractQuicDnsHandle
import com.frostnerd.encrypteddnstunnelproxy.quic.QuicUpstreamAddress
import com.frostnerd.encrypteddnstunnelproxy.tls.AbstractTLSDnsHandle
import kotlinx.android.synthetic.main.dialog_privacypolicy.view.*
import okhttp3.Dns
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.dnsoverhttps.DnsOverHttps
import okhttp3.internal.toHexString
import java.net.InetAddress
import java.util.*
/*
* Copyright (C) 2019 Daniel Wolf (Ch4t4r)
*
* 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 .
*
* You can contact the developer at daniel.wolf@frostnerd.com.
*/
fun showPrivacyPolicyDialog(context: Context) {
val dialog = AlertDialog.Builder(context, context.getPreferences().theme.dialogStyle)
dialog.setTitle(R.string.about_privacypolicy)
val view = LayoutInflater.from(context).inflate(R.layout.dialog_privacypolicy, null, false)
dialog.setView(view)
view.webView.loadUrl("file:///android_asset/privacy_policy.html")
dialog.setNeutralButton(R.string.all_close, null)
dialog.show()
}
fun showInfoTextDialogWithClose(
context: Context,
title: String,
text: String,
withDialog: (AlertDialog.() -> Unit)? = null,
) = showInfoTextDialog(context, title, text, neutralButton = null, positiveButton = context.getString(R.string.all_close) to null, withDialog = withDialog)
fun showInfoTextDialog(context:Context,
title:String,
text:String,
positiveButton:Pair Unit)?>? = null,
negativeButton:Pair Unit)?>? = null,
neutralButton:Pair Unit)?>? = context.getString(android.R.string.ok) to null,
withDialog: (AlertDialog.() -> Unit)? = null,
linkifyText:Boolean = false) {
try {
val stringWithLinks = SpannableString(text)
val dialogBuilder = AlertDialog.Builder(context, context.getPreferences().theme.dialogStyle)
.setTitle(title)
if(linkifyText) {
Linkify.addLinks(stringWithLinks, Linkify.ALL)
val span = Html.fromHtml(stringWithLinks.toString().replace("\n", "
"))
dialogBuilder.setMessage(span)
} else {
dialogBuilder.setMessage(text)
}
if(neutralButton != null) dialogBuilder.setNeutralButton(neutralButton.first, neutralButton.second)
if(positiveButton != null) dialogBuilder.setPositiveButton(positiveButton.first, positiveButton.second)
if(negativeButton != null) dialogBuilder.setNegativeButton(negativeButton.first, negativeButton.second)
val dialog = dialogBuilder.show()
if(linkifyText) {
val textView = dialog.findViewById(android.R.id.message)
textView?.movementMethod = LinkMovementMethod.getInstance()
textView?.linksClickable = true
textView?.setLinkTextColor(Color.parseColor("#64B5F6"))
}
withDialog?.invoke(dialog)
} catch (ex: Exception) {
if (ex is AndroidRuntimeException ||
ex.message?.let {
it.contains("webview", true) ||
it.contains("donor package", true)
} == true
) Toast.makeText(context, R.string.error_webview_missing, Toast.LENGTH_LONG)
.show()
else throw ex
}
}
fun isPackageInstalled(context: Context, packageName: String): Boolean {
val packageManager = context.packageManager
val intent = packageManager.getLaunchIntentForPackage(packageName) ?: return false
val list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
return list.size > 0
}
fun colorToHexString(@ColorInt color:Int):String {
return String.format(Locale.ROOT, "#%06X", 0xFFFFFF and color)
}
@ColorInt
fun opaqueColor(@ColorInt color: Int, opactiy: Int): Int {
val alpha = opactiy - opactiy % 5
return Color.parseColor("#" + alpha.toHexString() + colorToHexString(color).replace("#", ""))
}
interface BackpressFragment {
fun onBackPressed():Boolean
}
fun loadKnownDNSServers() {
AbstractHttpsDNSHandle // Loads the known servers.
AbstractTLSDnsHandle
AbstractQuicDnsHandle
KnownDnsServers
}
fun createQuicEngineIfInstalled(context: Context, quicOnly:Boolean, vararg addresses: QuicUpstreamAddress): QuicEngine? {
return if (QuicEngineImpl.providerInstalled) {
QuicEngineImpl(context, quicOnly, *addresses)
} else null
}
private fun httpClientWithoutDNS(): OkHttpClient {
return OkHttpClient.Builder().dns(object: Dns {
override fun lookup(hostname: String): List {
return emptyList()
}
}).build()
}
fun okhttpClientWithDoh(context: Context, forceDefaultFallback: Boolean = false): OkHttpClient {
val fallback = context.getPreferences().fallbackDns as HttpsDnsServerInformation?
if(fallback == null || forceDefaultFallback) {
val dns = DnsOverHttps.Builder().client(httpClientWithoutDNS())
.url("https://9.9.9.9/dns-query".toHttpUrl())
.bootstrapDnsHosts(
InetAddress.getByName("9.9.9.9"),
InetAddress.getByName("149.112.112.112"),
InetAddress.getByName("2620:fe::9"),
InetAddress.getByName("2620:fe::fe")
).build()
return OkHttpClient.Builder().dns(dns).build()
} else {
val url = fallback.servers.first().address.getUrl(false)
val hasKnownIPAddresses = fallback.servers.first().address.hasStaticAddresses
val dns = if(hasKnownIPAddresses) {
val addressCreator = fallback.servers.first().address.addressCreator
val addresses = addressCreator.resolveOrGetResultOrNull()?.toMutableList() ?: mutableListOf()
if (addresses.isNullOrEmpty() && addressCreator.hostAddress != null) {
addresses.add(InetAddress.getByName(addressCreator.hostAddress))
}
if (addresses.isNullOrEmpty()) {
DnsOverHttps.Builder().client(okhttpClientWithDoh(context, true))
.url(url.toHttpUrl()).build()
} else {
DnsOverHttps.Builder().client(httpClientWithoutDNS()).url(url.toHttpUrl())
.bootstrapDnsHosts(addresses)
.build()
}
} else {
DnsOverHttps.Builder().client(okhttpClientWithDoh(context, true)).url(url.toHttpUrl()).build()
}
return OkHttpClient.Builder().dns(dns).build()
}
}