mirror of
https://github.com/karasevm/PrivateDNSAndroid.git
synced 2025-06-29 04:39:56 +00:00
Quick tile Selection Dialog (#12)
* Feature Quick tile Server Selection dialog * Fix Bug * Add Toast Messages
This commit is contained in:
parent
96e345606e
commit
9aabfa6261
8 changed files with 243 additions and 43 deletions
|
@ -3,41 +3,53 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
|
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".PrivateDNSApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:name=".PrivateDNSApp"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.MyApplication">
|
android:theme="@style/Theme.Transparent">
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="rikka.shizuku.ShizukuProvider"
|
android:name="rikka.shizuku.ShizukuProvider"
|
||||||
android:authorities="${applicationId}.shizuku"
|
android:authorities="${applicationId}.shizuku"
|
||||||
android:multiprocess="false"
|
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:theme="@style/Theme.MyApplication"
|
||||||
|
android:taskAffinity="${applicationId}.main"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
|
||||||
android:name=".DnsTileService"
|
<activity
|
||||||
android:icon="@drawable/ic_unknown_black_24dp"
|
android:name=".SettingsDialogActivity"
|
||||||
android:label="@string/tile_name"
|
android:theme="@style/Theme.Transparent"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:excludeFromRecents="true"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
android:name="android.service.quicksettings.action.QS_TILE"/>
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".DnsTileService"
|
||||||
|
android:exported="true"
|
||||||
|
android:icon="@drawable/ic_unknown_black_24dp"
|
||||||
|
android:label="@string/tile_name"
|
||||||
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
|
@ -0,0 +1,88 @@
|
||||||
|
package ru.karasevm.privatednstoggle
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import ru.karasevm.privatednstoggle.databinding.SheetDnsSelectorBinding
|
||||||
|
import ru.karasevm.privatednstoggle.utils.PreferenceHelper.defaultPreference
|
||||||
|
import ru.karasevm.privatednstoggle.utils.PreferenceHelper.dns_servers
|
||||||
|
import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils
|
||||||
|
|
||||||
|
class DNSServerDialogFragment: DialogFragment() {
|
||||||
|
|
||||||
|
private var _binding: SheetDnsSelectorBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var linearLayoutManager: LinearLayoutManager
|
||||||
|
private lateinit var adapter: RecyclerAdapter
|
||||||
|
private var items = mutableListOf<String>()
|
||||||
|
private lateinit var sharedPrefs: SharedPreferences
|
||||||
|
|
||||||
|
override fun onCreateDialog(
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): Dialog {
|
||||||
|
return activity?.let {
|
||||||
|
val builder = MaterialAlertDialogBuilder(it)
|
||||||
|
val inflater = requireActivity().layoutInflater
|
||||||
|
_binding = SheetDnsSelectorBinding.inflate(inflater)
|
||||||
|
|
||||||
|
linearLayoutManager = LinearLayoutManager(context)
|
||||||
|
binding.recyclerView.layoutManager = linearLayoutManager
|
||||||
|
|
||||||
|
sharedPrefs = defaultPreference(requireContext())
|
||||||
|
items = sharedPrefs.dns_servers
|
||||||
|
if(items[0] == "") {
|
||||||
|
items.removeAt(0)
|
||||||
|
items.add("dns.google")
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = RecyclerAdapter(items)
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
builder.setTitle(R.string.select_server)
|
||||||
|
.setView(binding.root)
|
||||||
|
.setPositiveButton(R.string.done
|
||||||
|
) { _, _ ->
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
builder.create()
|
||||||
|
} ?: throw IllegalStateException("Activity cannot be null")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
val dnsMode = PrivateDNSUtils.getPrivateMode(requireActivity().contentResolver)
|
||||||
|
binding.autoSwitch.isChecked = dnsMode.lowercase() == "opportunistic"
|
||||||
|
|
||||||
|
adapter.onItemClick = { position ->
|
||||||
|
binding.autoSwitch.isChecked = false
|
||||||
|
val server = items[position]
|
||||||
|
PrivateDNSUtils.setPrivateMode(requireActivity().contentResolver, PrivateDNSUtils.DNS_MODE_PRIVATE)
|
||||||
|
PrivateDNSUtils.setPrivateProvider(requireActivity().contentResolver, server)
|
||||||
|
Toast.makeText(context, "DNS Server Set", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.autoSwitch.setOnClickListener {
|
||||||
|
if(binding.autoSwitch.isChecked) {
|
||||||
|
PrivateDNSUtils.setPrivateMode(requireActivity().contentResolver, PrivateDNSUtils.DNS_MODE_AUTO)
|
||||||
|
Toast.makeText(context, "DNS Server Set to Auto", Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
PrivateDNSUtils.setPrivateMode(requireActivity().contentResolver, PrivateDNSUtils.DNS_MODE_PRIVATE)
|
||||||
|
Toast.makeText(context, "DNS Server Set", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
activity?.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "DNSServerDialogFragment"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,34 +1,23 @@
|
||||||
package ru.karasevm.privatednstoggle
|
package ru.karasevm.privatednstoggle
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import android.widget.Toast
|
|
||||||
import ru.karasevm.privatednstoggle.utils.PreferenceHelper
|
import ru.karasevm.privatednstoggle.utils.PreferenceHelper
|
||||||
import ru.karasevm.privatednstoggle.utils.PreferenceHelper.autoMode
|
import ru.karasevm.privatednstoggle.utils.PreferenceHelper.autoMode
|
||||||
import ru.karasevm.privatednstoggle.utils.PreferenceHelper.dns_servers
|
import ru.karasevm.privatednstoggle.utils.PreferenceHelper.dns_servers
|
||||||
|
import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils
|
||||||
const val DNS_MODE_OFF = "off"
|
import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.DNS_MODE_AUTO
|
||||||
const val DNS_MODE_AUTO = "opportunistic"
|
import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.DNS_MODE_OFF
|
||||||
const val DNS_MODE_PRIVATE = "hostname"
|
import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.DNS_MODE_PRIVATE
|
||||||
|
import ru.karasevm.privatednstoggle.utils.PrivateDNSUtils.checkForPermission
|
||||||
|
|
||||||
class DnsTileService : TileService() {
|
class DnsTileService : TileService() {
|
||||||
|
|
||||||
|
|
||||||
private fun checkForPermission(): Boolean {
|
|
||||||
if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
Toast.makeText(this, R.string.permission_missing, Toast.LENGTH_SHORT).show()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTileAdded() {
|
override fun onTileAdded() {
|
||||||
super.onTileAdded()
|
super.onTileAdded()
|
||||||
checkForPermission()
|
checkForPermission(this)
|
||||||
// Update state
|
// Update state
|
||||||
qsTile.state = Tile.STATE_INACTIVE
|
qsTile.state = Tile.STATE_INACTIVE
|
||||||
|
|
||||||
|
@ -38,7 +27,7 @@ class DnsTileService : TileService() {
|
||||||
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
super.onClick()
|
super.onClick()
|
||||||
if (!checkForPermission()) {
|
if (!checkForPermission(this)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +93,7 @@ class DnsTileService : TileService() {
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
if (!checkForPermission()) {
|
if (!checkForPermission(this)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode")
|
val dnsMode = Settings.Global.getString(contentResolver, "private_dns_mode")
|
||||||
|
@ -137,16 +126,12 @@ class DnsTileService : TileService() {
|
||||||
)
|
)
|
||||||
} else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) {
|
} else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) {
|
||||||
val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier")
|
val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier")
|
||||||
if (dnsProvider != null) {
|
|
||||||
refreshTile(
|
refreshTile(
|
||||||
qsTile,
|
qsTile,
|
||||||
Tile.STATE_ACTIVE,
|
Tile.STATE_ACTIVE,
|
||||||
dnsProvider,
|
dnsProvider,
|
||||||
R.drawable.ic_private_black_24dp
|
R.drawable.ic_private_black_24dp
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Toast.makeText(this, R.string.permission_missing, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -187,8 +172,8 @@ class DnsTileService : TileService() {
|
||||||
tile.label = label
|
tile.label = label
|
||||||
tile.state = state
|
tile.state = state
|
||||||
tile.icon = Icon.createWithResource(this, icon)
|
tile.icon = Icon.createWithResource(this, icon)
|
||||||
Settings.Global.putString(contentResolver, "private_dns_mode", dnsMode)
|
PrivateDNSUtils.setPrivateMode(contentResolver, dnsMode)
|
||||||
Settings.Global.putString(contentResolver, "private_dns_specifier", dnsProvider)
|
PrivateDNSUtils.setPrivateProvider(contentResolver, dnsProvider)
|
||||||
tile.updateTile()
|
tile.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package ru.karasevm.privatednstoggle
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
|
||||||
|
class SettingsDialogActivity : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
val newFragment = DNSServerDialogFragment()
|
||||||
|
newFragment.show(supportFragmentManager, DNSServerDialogFragment.TAG)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ru.karasevm.privatednstoggle.utils
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.ContextCompat.checkSelfPermission
|
||||||
|
import ru.karasevm.privatednstoggle.R
|
||||||
|
|
||||||
|
object PrivateDNSUtils {
|
||||||
|
const val DNS_MODE_OFF = "off"
|
||||||
|
const val DNS_MODE_AUTO = "opportunistic"
|
||||||
|
const val DNS_MODE_PRIVATE = "hostname"
|
||||||
|
|
||||||
|
private const val PRIVATE_DNS_MODE = "private_dns_mode"
|
||||||
|
private const val PRIVATE_DNS_PROVIDER = "private_dns_specifier"
|
||||||
|
|
||||||
|
fun getPrivateMode(contentResolver: ContentResolver): String {
|
||||||
|
return Settings.Global.getString(contentResolver, PRIVATE_DNS_MODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPrivateProvider(contentResolver: ContentResolver): String {
|
||||||
|
return Settings.Global.getString(contentResolver, PRIVATE_DNS_PROVIDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrivateMode(contentResolver: ContentResolver, value: String) {
|
||||||
|
Settings.Global.putString(contentResolver, PRIVATE_DNS_MODE, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPrivateProvider(contentResolver: ContentResolver, value: String?) {
|
||||||
|
Settings.Global.putString(contentResolver, PRIVATE_DNS_PROVIDER, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkForPermission(context: Context): Boolean {
|
||||||
|
if (checkSelfPermission(context, Manifest.permission.WRITE_SECURE_SETTINGS) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Toast.makeText(context, R.string.permission_missing, Toast.LENGTH_SHORT).show()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
app/src/main/res/layout/sheet_dns_selector.xml
Normal file
44
app/src/main/res/layout/sheet_dns_selector.xml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/bottom_sheet"
|
||||||
|
style="@style/Widget.Material3.BottomSheet"
|
||||||
|
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginVertical="5dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.materialswitch.MaterialSwitch
|
||||||
|
android:id="@+id/auto_switch"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/menu_auto_enabled"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginVertical="5dp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxWidth="300dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
|
@ -9,7 +9,9 @@
|
||||||
<string name="menu_add">Add</string>
|
<string name="menu_add">Add</string>
|
||||||
<string name="menu_privacy_policy">Privacy Policy</string>
|
<string name="menu_privacy_policy">Privacy Policy</string>
|
||||||
<string name="menu_auto_enabled">Enable auto</string>
|
<string name="menu_auto_enabled">Enable auto</string>
|
||||||
|
<string name="select_server">Select Server</string>
|
||||||
<string name="auto_mode_clarification">Automatic (opportunistic) DNS mode will now be available in the tile</string>
|
<string name="auto_mode_clarification">Automatic (opportunistic) DNS mode will now be available in the tile</string>
|
||||||
|
<string name="done">Done</string>
|
||||||
<string name="cancel">Cancel</string>
|
<string name="cancel">Cancel</string>
|
||||||
<string name="delete_question">Delete</string>
|
<string name="delete_question">Delete</string>
|
||||||
<string name="delete_message">Are you sure you want to delete server?</string>
|
<string name="delete_message">Are you sure you want to delete server?</string>
|
||||||
|
|
|
@ -10,4 +10,17 @@
|
||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Theme.Transparent" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
<item name="android:background">@android:color/transparent</item>
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowAnimationStyle">@null</item>
|
||||||
|
|
||||||
|
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||||
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue