commit a9826bca363dc3c87f4de9bf86ed4286d73c88f1 Author: Maksim Karasev Date: Mon Aug 16 21:01:43 2021 +0300 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69eda01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,67 @@ +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..e82d84b --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Private DNS Quick Toggle \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..88ea3aa --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,122 @@ + + + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0d810e5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..551c5de --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94ca301 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Maksim Karasev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..a8ff265 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdk 30 + + defaultConfig { + applicationId "ru.karasevm.privatednstoggle" + minSdk 21 + targetSdk 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + targetSdkVersion 30 + minSdkVersion 28 + } + apply plugin: 'kotlin-android-extensions' + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.0' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/ru/karasevm/privatednstoggle/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ru/karasevm/privatednstoggle/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..aaa65d5 --- /dev/null +++ b/app/src/androidTest/java/ru/karasevm/privatednstoggle/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package ru.karasevm.privatednstoggle + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("ru.karasevm.privatednstoggle", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..712035b --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..504c7ac Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt new file mode 100644 index 0000000..94957d6 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/AddServerDialogFragment.kt @@ -0,0 +1,62 @@ +package ru.karasevm.privatednstoggle + +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.dialog_add.* +import kotlinx.android.synthetic.main.dialog_add.view.* + + +class AddServerDialogFragment(): DialogFragment() { + // Use this instance of the interface to deliver action events + internal lateinit var listener: NoticeDialogListener + + /* The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. */ + interface NoticeDialogListener { + fun onDialogPositiveClick(dialog: DialogFragment, server: String) + } + + // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener + override fun onAttach(context: Context) { + super.onAttach(context) + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + listener = context as NoticeDialogListener + } catch (e: ClassCastException) { + // The activity doesn't implement the interface, throw exception + throw ClassCastException((context.toString() + + " must implement NoticeDialogListener")) + } + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { + val builder = AlertDialog.Builder(it) + // Get the layout inflater + val inflater = requireActivity().layoutInflater; + val view = inflater.inflate(R.layout.dialog_add, null) + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + builder.setTitle(R.string.add_server) + .setView(view) + // Add action buttons + .setPositiveButton(R.string.add, + DialogInterface.OnClickListener { dialog, id -> + listener.onDialogPositiveClick(this,view.editTextServerAddr.text.toString()) + }) + .setNegativeButton(R.string.cancel, + DialogInterface.OnClickListener { dialog, id -> + getDialog()?.cancel() + }) + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/DeleteServerDialogFragment.kt b/app/src/main/java/ru/karasevm/privatednstoggle/DeleteServerDialogFragment.kt new file mode 100644 index 0000000..8146772 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/DeleteServerDialogFragment.kt @@ -0,0 +1,61 @@ +package ru.karasevm.privatednstoggle + +import android.app.Dialog +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import kotlinx.android.synthetic.main.dialog_add.* +import kotlinx.android.synthetic.main.dialog_add.view.* + + +class DeleteServerDialogFragment(val position: Int): DialogFragment() { + // Use this instance of the interface to deliver action events + internal lateinit var listener: NoticeDialogListener + + /* The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. */ + interface NoticeDialogListener { + fun onDialogPositiveClick(dialog: DialogFragment, position: Int) + } + + // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener + override fun onAttach(context: Context) { + super.onAttach(context) + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + listener = context as NoticeDialogListener + } catch (e: ClassCastException) { + // The activity doesn't implement the interface, throw exception + throw ClassCastException((context.toString() + + " must implement NoticeDialogListener")) + } + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return activity?.let { + val builder = AlertDialog.Builder(it) + // Get the layout inflater + val inflater = requireActivity().layoutInflater; + val view = inflater.inflate(R.layout.dialog_add, null) + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + builder.setMessage(R.string.delete_question) + .setPositiveButton(R.string.delete, + DialogInterface.OnClickListener { dialog, id -> + listener.onDialogPositiveClick(this, position) + }) + .setNegativeButton(R.string.cancel, + DialogInterface.OnClickListener { dialog, id -> + getDialog()?.cancel() + }) + // Create the AlertDialog object and return it + builder.create() + } ?: throw IllegalStateException("Activity cannot be null") + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt b/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt new file mode 100644 index 0000000..26df49c --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/DnsTileService.kt @@ -0,0 +1,192 @@ +package ru.karasevm.privatednstoggle + +import android.Manifest +import android.content.pm.PackageManager +import android.graphics.drawable.Icon +import android.provider.Settings +import android.service.quicksettings.Tile +import android.service.quicksettings.TileService +import android.util.Log +import android.widget.Toast + + +const val DNS_MODE_OFF = "off"; +const val DNS_MODE_AUTO = "opportunistic"; +const val DNS_MODE_PRIVATE = "hostname"; + +class DnsTileService : TileService() { + + + 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() { + super.onTileAdded() + checkForPermission() + // Update state + qsTile.state = Tile.STATE_INACTIVE + + // Update looks + qsTile.updateTile() + } + + override fun onClick() { + super.onClick() + if (!checkForPermission()) { + return + } + + val dnsMode = Settings.Global.getString(getContentResolver(), "private_dns_mode"); + val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier") + + if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) { +// refreshTile(qsTile, Tile.STATE_INACTIVE, getString(R.string.dns_off), R.drawable.ic_off_black_24dp) + changeTileState( + qsTile, + Tile.STATE_ACTIVE, + getNextAddress(dnsProvider), + R.drawable.ic_private_black_24dp, + DNS_MODE_PRIVATE, + getNextAddress(dnsProvider) + ) + } else if (dnsMode == null || dnsMode.equals(DNS_MODE_AUTO, ignoreCase = true)) { + changeTileState( + qsTile, + Tile.STATE_ACTIVE, + getNextAddress(dnsProvider), + R.drawable.ic_private_black_24dp, + DNS_MODE_PRIVATE, + getNextAddress(dnsProvider) + ) + } else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) { + if (getNextAddress(dnsProvider) == null) { + changeTileState( + qsTile, + Tile.STATE_INACTIVE, + getString(R.string.dns_off), + R.drawable.ic_off_black_24dp, + DNS_MODE_OFF, + getNextAddress(dnsProvider) + ) + } else { + changeTileState( + qsTile, + Tile.STATE_ACTIVE, + getNextAddress(dnsProvider), + R.drawable.ic_private_black_24dp, + DNS_MODE_PRIVATE, + getNextAddress(dnsProvider) + ) + } + } + + } + + override fun onStartListening() { + super.onStartListening() + var dnsMode = Settings.Global.getString(getContentResolver(), "private_dns_mode"); + Log.d("TEMP", "onStartListening: called " + dnsMode) + if (dnsMode.equals(DNS_MODE_OFF, ignoreCase = true)) { + refreshTile( + qsTile, + Tile.STATE_INACTIVE, + getString(R.string.dns_off), + R.drawable.ic_off_black_24dp + ) + } else if (dnsMode == null || dnsMode.equals(DNS_MODE_AUTO, ignoreCase = true)) { + refreshTile( + qsTile, + Tile.STATE_INACTIVE, + getString(R.string.dns_auto), + R.drawable.ic_auto_black_24dp + ) + } else if (dnsMode.equals(DNS_MODE_PRIVATE, ignoreCase = true)) { + val dnsProvider = Settings.Global.getString(contentResolver, "private_dns_specifier") + if (dnsProvider != null) { + refreshTile( + qsTile, + Tile.STATE_ACTIVE, + dnsProvider, + R.drawable.ic_private_black_24dp + ) + } else { + Toast.makeText(this, R.string.permission_missing, Toast.LENGTH_SHORT).show() + } + } + + } + + /** + * Updates tile to specified parameters + * + * @param tile tile to update + * @param state tile state + * @param label tile label + * @param icon tile icon + */ + private fun refreshTile(tile: Tile, state: Int, label: String?, icon: Int) { + tile.state = state + tile.label = label + tile.icon = Icon.createWithResource(this, icon) + tile.updateTile() + } + + /** + * Updates tile and system settings to specified parameters + * + * @param tile tile to update + * @param state tile state + * @param label tile label + * @param icon tile icon + * @param dnsMode system dns mode + * @param dnsProvider system dns provider + */ + private fun changeTileState( + tile: Tile, + state: Int, + label: String?, + icon: Int, + dnsMode: String, + dnsProvider: String? + ) { + tile.label = label + tile.state = state + tile.icon = Icon.createWithResource(this, icon) + Settings.Global.putString(contentResolver, "private_dns_mode", dnsMode) + Settings.Global.putString(contentResolver, "private_dns_specifier", dnsProvider) + tile.updateTile() + } + + /** + * Gets next dns address from preferences, + * if current address is last returns null + * + * @param currentAddress currently set address + * @return next address + */ + private fun getNextAddress(currentAddress: String?): String? { + val sharedPrefs = this.getSharedPreferences("app_prefs", 0); + val items = sharedPrefs.getString("dns_servers", "dns.google")!!.split(",").toMutableList() + + // Fallback if list is empty + if (items[0] == "") { + items.removeAt(0) + items.add("dns.google") + } + + val index = items.indexOf(currentAddress) + + if (index == -1 || currentAddress == null) { + return items[0] + } + if (index == items.size - 1) { + return null + } + return items[index + 1] + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt b/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt new file mode 100644 index 0000000..21b05e6 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/MainActivity.kt @@ -0,0 +1,98 @@ +package ru.karasevm.privatednstoggle + +import android.Manifest +import android.content.SharedPreferences +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.widget.Toast +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter +import kotlinx.android.synthetic.main.activity_main.* +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri + + +class MainActivity : AppCompatActivity(), AddServerDialogFragment.NoticeDialogListener, DeleteServerDialogFragment.NoticeDialogListener { + + private lateinit var linearLayoutManager: LinearLayoutManager + public var items = mutableListOf() + lateinit var sharedPrefs: SharedPreferences + lateinit var adapter: RecyclerAdapter + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + if (checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com")) //TODO: REPLACE LINK + startActivity(browserIntent) + finish() + } + linearLayoutManager = LinearLayoutManager(this) + recyclerView.layoutManager = linearLayoutManager + + sharedPrefs = this.getSharedPreferences("app_prefs", 0); + + items = sharedPrefs.getString("dns_servers", "")!!.split(",").toMutableList() + if (items[0] == "") { + items.removeAt(0) + } + adapter = RecyclerAdapter(items) + adapter.onItemClick = { position -> + val newFragment = DeleteServerDialogFragment(position) + newFragment.show(supportFragmentManager, "delete_server") + } + recyclerView.adapter = adapter + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.add_server -> { + val newFragment = AddServerDialogFragment() + newFragment.show(supportFragmentManager, "add_server") + true + } + R.id.privacy_policy -> { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"))//TODO: REPLACE LINK + startActivity(browserIntent) + true + } + + else -> { + // If we got here, the user's action was not recognized. + // Invoke the superclass to handle it. + super.onOptionsItemSelected(item) + } + } + + override fun onDialogPositiveClick(dialog: DialogFragment, server: String) { + if (server.length == 0) { + Toast.makeText(this, R.string.server_length_error, Toast.LENGTH_SHORT).show() + return + } + items.add(server) + adapter.setData(items.toMutableList()) + recyclerView.adapter?.notifyItemInserted(items.size - 1) + sharedPrefs.edit() + .putString("dns_servers", items.joinToString(separator = ",") { it -> it }).commit() + } + + override fun onDialogPositiveClick(dialog: DialogFragment,position: Int) { + items.removeAt(position) + adapter.setData(items.toMutableList()) +// adapter.notifyItemRangeChanged(position, items.size - position -2) + adapter.notifyDataSetChanged() // TODO: DON'T USE THIS + sharedPrefs.edit() + .putString("dns_servers", items.joinToString(separator = ",") { it -> it }).commit() + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt b/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt new file mode 100644 index 0000000..e0949a8 --- /dev/null +++ b/app/src/main/java/ru/karasevm/privatednstoggle/RecyclerAdapter.kt @@ -0,0 +1,50 @@ +package ru.karasevm.privatednstoggle + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import java.util.* + +class RecyclerAdapter(val items: MutableList): RecyclerView.Adapter() { + + var onItemClick: ((Int) -> Unit)? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerAdapter.ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_row, parent, false) + val vh = ViewHolder(view) + return vh + } + + override fun onBindViewHolder(holder: RecyclerAdapter.ViewHolder, position: Int) { + val item = items[position] + + // sets the text to the textview from our itemHolder class + holder.textView.text = item + } + + override fun getItemCount(): Int { + return items.size + } + + + + inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(R.id.textView) + init { + itemView.setOnClickListener { + onItemClick?.invoke(adapterPosition) + } + } + } + fun setData(newItems: MutableList) { + items.run { + clear() + addAll(newItems) + } + } + + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-anydpi/ic_tile_default.xml b/app/src/main/res/drawable-anydpi/ic_tile_default.xml new file mode 100644 index 0000000..2af57be --- /dev/null +++ b/app/src/main/res/drawable-anydpi/ic_tile_default.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable-hdpi/ic_tile_default.png b/app/src/main/res/drawable-hdpi/ic_tile_default.png new file mode 100644 index 0000000..9809f36 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_tile_default.png b/app/src/main/res/drawable-mdpi/ic_tile_default.png new file mode 100644 index 0000000..cb09290 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_tile_default.png b/app/src/main/res/drawable-xhdpi/ic_tile_default.png new file mode 100644 index 0000000..5564007 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_tile_default.png b/app/src/main/res/drawable-xxhdpi/ic_tile_default.png new file mode 100644 index 0000000..35f5c5f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_tile_default.png differ diff --git a/app/src/main/res/drawable/ic_auto_black_24dp.xml b/app/src/main/res/drawable/ic_auto_black_24dp.xml new file mode 100644 index 0000000..3ddd864 --- /dev/null +++ b/app/src/main/res/drawable/ic_auto_black_24dp.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 0000000..70046c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..b2e4c59 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_off_black_24dp.xml b/app/src/main/res/drawable/ic_off_black_24dp.xml new file mode 100644 index 0000000..d385ee3 --- /dev/null +++ b/app/src/main/res/drawable/ic_off_black_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_private_black_24dp.xml b/app/src/main/res/drawable/ic_private_black_24dp.xml new file mode 100644 index 0000000..f01d8ca --- /dev/null +++ b/app/src/main/res/drawable/ic_private_black_24dp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0ff5544 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_add.xml b/app/src/main/res/layout/dialog_add.xml new file mode 100644 index 0000000..8520eb5 --- /dev/null +++ b/app/src/main/res/layout/dialog_add.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recyclerview_row.xml b/app/src/main/res/layout/recyclerview_row.xml new file mode 100644 index 0000000..fa7bb16 --- /dev/null +++ b/app/src/main/res/layout/recyclerview_row.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 0000000..49e94e8 --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..05b82a8 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..cdd2b1b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..b0a456c Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..f61ced5 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..4f1fa21 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..7d80cf5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..89f96ba Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..bdb278e Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..1cd6644 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..2c7bdb7 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..203e219 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..846e773 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #3773C1 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0f6d22d --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + Private DNS Quick Toggle + Test + Permission not granted, check app to see how to do it + Off + Auto + Add Server + Add + Cancel + Delete server? + Delete + Server address cannot be empty + Privacy Policy + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..eebc2d0 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/test/java/ru/karasevm/privatednstoggle/ExampleUnitTest.kt b/app/src/test/java/ru/karasevm/privatednstoggle/ExampleUnitTest.kt new file mode 100644 index 0000000..3ad9f4e --- /dev/null +++ b/app/src/test/java/ru/karasevm/privatednstoggle/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package ru.karasevm.privatednstoggle + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5f1384b --- /dev/null +++ b/build.gradle @@ -0,0 +1,18 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath "com.android.tools.build:gradle:7.0.0" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..98bed16 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..7dfaa76 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 16 15:36:35 MSK 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..9e0b6d2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + jcenter() // Warning: this repository is going to shut down soon + } +} +rootProject.name = "Private DNS Quick Toggle" +include ':app'