mirror of
https://github.com/dovecoteescapee/ByeDPIAndroid.git
synced 2024-12-21 22:06:22 +00:00
Merge pull request #74 from dovecoteescapee/dev
Add command line style settings and refactor settings
This commit is contained in:
commit
18fcf9262c
@ -19,8 +19,7 @@
|
||||
tools:targetApi="34">
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance">
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
|
@ -10,3 +10,9 @@ int get_addr(const char *str, struct sockaddr_ina *addr);
|
||||
void *add(void **root, int *n, size_t ss);
|
||||
|
||||
void clear_params(void);
|
||||
|
||||
char *ftob(const char *str, ssize_t *sl);
|
||||
|
||||
struct mphdr *parse_hosts(char *buffer, size_t size);
|
||||
|
||||
int parse_offset(struct part *part, const char *str);
|
||||
|
@ -19,12 +19,43 @@ const enum demode DESYNC_METHODS[] = {
|
||||
DESYNC_OOB,
|
||||
};
|
||||
|
||||
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, __attribute__((unused)) void *reserved) {
|
||||
oob_data.data = NULL;
|
||||
JNIEXPORT jint JNI_OnLoad(
|
||||
__attribute__((unused)) JavaVM *vm,
|
||||
__attribute__((unused)) void *reserved) {
|
||||
default_params = params;
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocketWithCommandLine(
|
||||
JNIEnv *env,
|
||||
__attribute__((unused)) jobject thiz,
|
||||
jobjectArray args) {
|
||||
int argc = (*env)->GetArrayLength(env, args);
|
||||
char *argv[argc];
|
||||
for (int i = 0; i < argc; i++) {
|
||||
jstring arg = (jstring) (*env)->GetObjectArrayElement(env, args, i);
|
||||
const char *arg_str = (*env)->GetStringUTFChars(env, arg, 0);
|
||||
argv[i] = strdup(arg_str);
|
||||
(*env)->ReleaseStringUTFChars(env, arg, arg_str);
|
||||
}
|
||||
|
||||
int res = parse_args(argc, argv);
|
||||
if (res < 0) {
|
||||
uniperror("parse_args");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd = listen_socket((struct sockaddr_ina *)¶ms.laddr);
|
||||
if (fd < 0) {
|
||||
uniperror("listen_socket");
|
||||
return -1;
|
||||
}
|
||||
LOG(LOG_S, "listen_socket, fd: %d", fd);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
|
||||
JNIEnv *env,
|
||||
@ -36,6 +67,9 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
|
||||
jint default_ttl,
|
||||
jboolean custom_ttl,
|
||||
jboolean no_domain,
|
||||
jboolean desync_http,
|
||||
jboolean desync_https,
|
||||
jboolean desync_udp,
|
||||
jint desync_method,
|
||||
jint split_position,
|
||||
jboolean split_at_host,
|
||||
@ -90,6 +124,10 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
|
||||
}
|
||||
|
||||
dp->ttl = fake_ttl;
|
||||
dp->proto =
|
||||
IS_HTTP * desync_http |
|
||||
IS_HTTPS * desync_https |
|
||||
IS_UDP * desync_udp;
|
||||
dp->mod_http =
|
||||
MH_HMIX * host_mixed_case |
|
||||
MH_DMIX * domain_mixed_case |
|
||||
@ -108,7 +146,9 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
|
||||
|
||||
enum demode mode = DESYNC_METHODS[desync_method];
|
||||
|
||||
part->flag = split_at_host ? OFFSET_SNI : 0;
|
||||
int offset_flag = dp->proto || desync_https ? OFFSET_SNI : OFFSET_HOST;
|
||||
|
||||
part->flag = split_at_host ? offset_flag : 0;
|
||||
part->pos = split_position;
|
||||
part->m = mode;
|
||||
|
||||
@ -125,7 +165,7 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
|
||||
return -1;
|
||||
}
|
||||
|
||||
tlsrec_part->flag = tls_record_split_at_sni ? OFFSET_SNI : 0;
|
||||
tlsrec_part->flag = tls_record_split_at_sni ? offset_flag : 0;
|
||||
tlsrec_part->pos = tls_record_split_position;
|
||||
}
|
||||
|
||||
@ -154,6 +194,16 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniCreateSocket(
|
||||
(*env)->ReleaseStringUTFChars(env, custom_oob_data, oob);
|
||||
}
|
||||
|
||||
if (dp->proto) {
|
||||
dp = add((void *)¶ms.dp,
|
||||
¶ms.dp_count, sizeof(struct desync_params));
|
||||
if (!dp) {
|
||||
uniperror("add");
|
||||
clear_params();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
params.mempool = mem_pool(0);
|
||||
if (!params.mempool) {
|
||||
uniperror("mem_pool");
|
||||
@ -200,4 +250,4 @@ Java_io_github_dovecoteescapee_byedpi_core_ByeDpiProxy_jniStopProxy(
|
||||
return get_e();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -1,6 +1,12 @@
|
||||
#include "utils.h"
|
||||
#include <getopt.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "byedpi/params.h"
|
||||
#include "error.h"
|
||||
#include "main.h"
|
||||
#include "packets.h"
|
||||
#include "utils.h"
|
||||
|
||||
struct params default_params;
|
||||
|
||||
@ -8,3 +14,413 @@ void reset_params(void) {
|
||||
clear_params();
|
||||
params = default_params;
|
||||
}
|
||||
|
||||
extern const struct option options[35];
|
||||
|
||||
int parse_args(int argc, char **argv) {
|
||||
int optc = sizeof(options)/sizeof(*options);
|
||||
for (int i = 0, e = optc; i < e; i++)
|
||||
optc += options[i].has_arg;
|
||||
|
||||
char opt[optc + 1];
|
||||
opt[optc] = 0;
|
||||
|
||||
for (int i = 0, o = 0; o < optc; i++, o++) {
|
||||
opt[o] = options[i].val;
|
||||
for (int c = options[i].has_arg; c; c--) {
|
||||
o++;
|
||||
opt[o] = ':';
|
||||
}
|
||||
}
|
||||
|
||||
params.laddr.sin6_port = htons(1080);
|
||||
|
||||
int rez;
|
||||
int invalid = 0;
|
||||
|
||||
long val;
|
||||
char *end = 0;
|
||||
|
||||
struct desync_params *dp = add((void *)¶ms.dp,
|
||||
¶ms.dp_count, sizeof(struct desync_params));
|
||||
if (!dp) {
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
|
||||
optind = optreset = 1;
|
||||
|
||||
while (!invalid && (rez = getopt_long_only(
|
||||
argc, argv, opt, options, 0)) != -1) {
|
||||
switch (rez) {
|
||||
|
||||
case 'N':
|
||||
params.resolve = 0;
|
||||
break;
|
||||
case 'X':
|
||||
params.ipv6 = 0;
|
||||
break;
|
||||
case 'U':
|
||||
params.udp = 0;
|
||||
break;
|
||||
|
||||
case 'i':
|
||||
if (get_addr(optarg,
|
||||
(struct sockaddr_ina *)¶ms.laddr) < 0)
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > 0xffff || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.laddr.sin6_port = htons(val);
|
||||
break;
|
||||
|
||||
case 'I':
|
||||
if (get_addr(optarg,
|
||||
(struct sockaddr_ina *)¶ms.baddr) < 0)
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > INT_MAX/4 || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.bfsize = val;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val >= (0xffff/2) || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.max_open = val;
|
||||
break;
|
||||
|
||||
case 'x': //
|
||||
params.debug = strtol(optarg, 0, 0);
|
||||
if (params.debug < 0)
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
// desync options
|
||||
|
||||
case 'F':
|
||||
params.tfo = 1;
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
dp = add((void *)¶ms.dp, ¶ms.dp_count,
|
||||
sizeof(struct desync_params));
|
||||
if (!dp) {
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
if (!optarg) {
|
||||
dp->detect |= DETECT_TORST;
|
||||
break;
|
||||
}
|
||||
end = optarg;
|
||||
while (end && !invalid) {
|
||||
switch (*end) {
|
||||
case 't':
|
||||
dp->detect |= DETECT_TORST;
|
||||
break;
|
||||
case 'r':
|
||||
dp->detect |= DETECT_HTTP_LOCAT;
|
||||
break;
|
||||
case 'c':
|
||||
dp->detect |= DETECT_HTTP_CLERR;
|
||||
break;
|
||||
case 's':
|
||||
dp->detect |= DETECT_TLS_INVSID;
|
||||
break;
|
||||
case 'a':
|
||||
dp->detect |= DETECT_TLS_ALERT;
|
||||
break;
|
||||
case 'n':
|
||||
break;
|
||||
default:
|
||||
invalid = 1;
|
||||
continue;
|
||||
}
|
||||
end = strchr(end, ',');
|
||||
if (end) end++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.cache_ttl = val;
|
||||
break;
|
||||
|
||||
case 'T':;
|
||||
float f = strtof(optarg, &end);
|
||||
val = (long)(f * 1000);
|
||||
if (val <= 0 || val > UINT_MAX || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
params.timeout = val;
|
||||
break;
|
||||
|
||||
case 'K':
|
||||
end = optarg;
|
||||
while (end && !invalid) {
|
||||
switch (*end) {
|
||||
case 't':
|
||||
dp->proto |= IS_HTTPS;
|
||||
break;
|
||||
case 'h':
|
||||
dp->proto |= IS_HTTP;
|
||||
break;
|
||||
case 'u':
|
||||
dp->proto |= IS_UDP;
|
||||
break;
|
||||
default:
|
||||
invalid = 1;
|
||||
continue;
|
||||
}
|
||||
end = strchr(end, ',');
|
||||
if (end) end++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'H':;
|
||||
if (dp->file_ptr) {
|
||||
continue;
|
||||
}
|
||||
dp->file_ptr = ftob(optarg, &dp->file_size);
|
||||
if (!dp->file_ptr) {
|
||||
uniperror("read/parse");
|
||||
invalid = 1;
|
||||
continue;
|
||||
}
|
||||
dp->hosts = parse_hosts(dp->file_ptr, dp->file_size);
|
||||
if (!dp->hosts) {
|
||||
perror("parse_hosts");
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's':
|
||||
case 'd':
|
||||
case 'o':
|
||||
case 'f':
|
||||
;
|
||||
struct part *part = add((void *)&dp->parts,
|
||||
&dp->parts_n, sizeof(struct part));
|
||||
if (!part) {
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
if (parse_offset(part, optarg)) {
|
||||
invalid = 1;
|
||||
break;
|
||||
}
|
||||
switch (rez) {
|
||||
case 's': part->m = DESYNC_SPLIT;
|
||||
break;
|
||||
case 'd': part->m = DESYNC_DISORDER;
|
||||
break;
|
||||
case 'o': part->m = DESYNC_OOB;
|
||||
break;
|
||||
case 'f': part->m = DESYNC_FAKE;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > 255 || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
dp->ttl = val;
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
if (dp->ip_options) {
|
||||
continue;
|
||||
}
|
||||
if (optarg)
|
||||
dp->ip_options = ftob(optarg, &dp->ip_options_len);
|
||||
else {
|
||||
dp->ip_options = ip_option;
|
||||
dp->ip_options_len = sizeof(ip_option);
|
||||
}
|
||||
if (!dp->ip_options) {
|
||||
uniperror("read/parse");
|
||||
invalid = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
dp->md5sig = 1;
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (change_tls_sni(optarg, fake_tls.data, fake_tls.size)) {
|
||||
fprintf(stderr, "error chsni\n");
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
printf("sni: %s\n", optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (dp->fake_data.data) {
|
||||
continue;
|
||||
}
|
||||
dp->fake_data.data = ftob(optarg, &dp->fake_data.size);
|
||||
if (!dp->fake_data.data) {
|
||||
uniperror("read/parse");
|
||||
invalid = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
if (oob_data.data != oob_char) {
|
||||
continue;
|
||||
}
|
||||
oob_data.data = ftob(optarg, &oob_data.size);
|
||||
if (!oob_data.data) {
|
||||
uniperror("read/parse");
|
||||
invalid = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
end = optarg;
|
||||
while (end && !invalid) {
|
||||
switch (*end) {
|
||||
case 'r':
|
||||
dp->mod_http |= MH_SPACE;
|
||||
break;
|
||||
case 'h':
|
||||
dp->mod_http |= MH_HMIX;
|
||||
break;
|
||||
case 'd':
|
||||
dp->mod_http |= MH_DMIX;
|
||||
break;
|
||||
default:
|
||||
invalid = 1;
|
||||
continue;
|
||||
}
|
||||
end = strchr(end, ',');
|
||||
if (end) end++;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
part = add((void *)&dp->tlsrec,
|
||||
&dp->tlsrec_n, sizeof(struct part));
|
||||
if (!part) {
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
if (parse_offset(part, optarg)
|
||||
|| part->pos > 0xffff) {
|
||||
invalid = 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val < 0 || val > INT_MAX || *end)
|
||||
invalid = 1;
|
||||
else
|
||||
dp->udp_fake_count = val;
|
||||
break;
|
||||
|
||||
case 'V':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > USHRT_MAX)
|
||||
invalid = 1;
|
||||
else {
|
||||
dp->pf[0] = htons(val);
|
||||
if (*end == '-') {
|
||||
val = strtol(end + 1, &end, 0);
|
||||
if (val <= 0 || val > USHRT_MAX)
|
||||
invalid = 1;
|
||||
}
|
||||
if (*end)
|
||||
invalid = 1;
|
||||
else
|
||||
dp->pf[1] = htons(val);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
val = strtol(optarg, &end, 0);
|
||||
if (val <= 0 || val > 255 || *end)
|
||||
invalid = 1;
|
||||
else {
|
||||
params.def_ttl = val;
|
||||
params.custom_ttl = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'w': //
|
||||
params.sfdelay = strtol(optarg, &end, 0);
|
||||
if (params.sfdelay < 0 || optarg == end
|
||||
|| params.sfdelay >= 1000 || *end)
|
||||
invalid = 1;
|
||||
break;
|
||||
|
||||
case 'W':
|
||||
params.wait_send = 0;
|
||||
break;
|
||||
case 'P':
|
||||
params.protect_path = optarg;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case '?':
|
||||
reset_params();
|
||||
return -1;
|
||||
|
||||
default:
|
||||
printf("?: %c\n", rez);
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (invalid) {
|
||||
fprintf(stderr, "invalid value: -%c %s\n", rez, optarg);
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
if (dp->hosts || dp->proto || dp->pf[0]) {
|
||||
dp = add((void *)¶ms.dp,
|
||||
¶ms.dp_count, sizeof(struct desync_params));
|
||||
if (!dp) {
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.baddr.sin6_family != AF_INET6) {
|
||||
params.ipv6 = 0;
|
||||
}
|
||||
if (!params.def_ttl) {
|
||||
if ((params.def_ttl = get_default_ttl()) < 1) {
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
params.mempool = mem_pool(0);
|
||||
if (!params.mempool) {
|
||||
uniperror("mem_pool");
|
||||
reset_params();
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
extern struct params default_params;
|
||||
|
||||
void reset_params(void);
|
||||
int parse_args(int argc, char **argv);
|
||||
|
@ -19,19 +19,12 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.data.AppStatus
|
||||
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Mode
|
||||
import io.github.dovecoteescapee.byedpi.data.SENDER
|
||||
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Sender
|
||||
import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment
|
||||
import io.github.dovecoteescapee.byedpi.data.*
|
||||
import io.github.dovecoteescapee.byedpi.fragments.MainSettingsFragment
|
||||
import io.github.dovecoteescapee.byedpi.databinding.ActivityMainBinding
|
||||
import io.github.dovecoteescapee.byedpi.services.ServiceManager
|
||||
import io.github.dovecoteescapee.byedpi.services.appStatus
|
||||
import io.github.dovecoteescapee.byedpi.utility.getPreferences
|
||||
import io.github.dovecoteescapee.byedpi.utility.mode
|
||||
import io.github.dovecoteescapee.byedpi.utility.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
@ -154,9 +147,9 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
val theme = getPreferences(this)
|
||||
val theme = getPreferences()
|
||||
.getString("app_theme", null)
|
||||
SettingsFragment.setTheme(theme ?: "system")
|
||||
MainSettingsFragment.setTheme(theme ?: "system")
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||
ContextCompat.checkSelfPermission(
|
||||
@ -215,7 +208,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun start() {
|
||||
when (getPreferences(this).mode()) {
|
||||
when (getPreferences().mode()) {
|
||||
Mode.VPN -> {
|
||||
val intentPrepare = VpnService.prepare(this)
|
||||
if (intentPrepare != null) {
|
||||
@ -238,9 +231,9 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
Log.i(TAG, "Updating status: $status, $mode")
|
||||
|
||||
val preferences = getPreferences(this)
|
||||
val proxyIp = preferences.getString("byedpi_proxy_ip", null) ?: "127.0.0.1"
|
||||
val proxyPort = preferences.getString("byedpi_proxy_port", null) ?: "1080"
|
||||
val preferences = getPreferences()
|
||||
val proxyIp = preferences.getStringNotNull("byedpi_proxy_ip", "127.0.0.1")
|
||||
val proxyPort = preferences.getStringNotNull("byedpi_proxy_port", "1080")
|
||||
binding.proxyAddress.text = getString(R.string.proxy_address, proxyIp, proxyPort)
|
||||
|
||||
when (status) {
|
||||
|
@ -5,7 +5,7 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.fragments.SettingsFragment
|
||||
import io.github.dovecoteescapee.byedpi.fragments.MainSettingsFragment
|
||||
import io.github.dovecoteescapee.byedpi.utility.getPreferences
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
@ -15,7 +15,7 @@ class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, SettingsFragment())
|
||||
.replace(R.id.settings, MainSettingsFragment())
|
||||
.commit()
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
@ -33,14 +33,11 @@ class SettingsActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
R.id.action_reset_settings -> {
|
||||
getPreferences(this)
|
||||
.edit()
|
||||
.clear()
|
||||
.apply()
|
||||
getPreferences().edit().clear().apply()
|
||||
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, SettingsFragment())
|
||||
.replace(R.id.settings, MainSettingsFragment())
|
||||
.commit()
|
||||
true
|
||||
}
|
||||
|
@ -41,7 +41,19 @@ class ByeDpiProxy {
|
||||
throw IllegalStateException("Proxy is already running")
|
||||
}
|
||||
|
||||
val fd = jniCreateSocket(
|
||||
val fd = createSocketFromPreferences(preferences)
|
||||
if (fd < 0) {
|
||||
return -1
|
||||
}
|
||||
this.fd = fd
|
||||
fd
|
||||
}
|
||||
|
||||
private fun createSocketFromPreferences(preferences: ByeDpiProxyPreferences) =
|
||||
when (preferences) {
|
||||
is ByeDpiProxyCmdPreferences -> jniCreateSocketWithCommandLine(preferences.args)
|
||||
|
||||
is ByeDpiProxyUIPreferences -> jniCreateSocket(
|
||||
ip = preferences.ip,
|
||||
port = preferences.port,
|
||||
maxConnections = preferences.maxConnections,
|
||||
@ -49,6 +61,9 @@ class ByeDpiProxy {
|
||||
defaultTtl = preferences.defaultTtl,
|
||||
customTtl = preferences.customTtl,
|
||||
noDomain = preferences.noDomain,
|
||||
desyncHttp = preferences.desyncHttp,
|
||||
desyncHttps = preferences.desyncHttps,
|
||||
desyncUdp = preferences.desyncUdp,
|
||||
desyncMethod = preferences.desyncMethod.ordinal,
|
||||
splitPosition = preferences.splitPosition,
|
||||
splitAtHost = preferences.splitAtHost,
|
||||
@ -62,15 +77,10 @@ class ByeDpiProxy {
|
||||
tlsRecordSplitPosition = preferences.tlsRecordSplitPosition,
|
||||
tlsRecordSplitAtSni = preferences.tlsRecordSplitAtSni,
|
||||
)
|
||||
|
||||
if (fd < 0) {
|
||||
return -1
|
||||
}
|
||||
|
||||
this.fd = fd
|
||||
fd
|
||||
}
|
||||
|
||||
private external fun jniCreateSocketWithCommandLine(args: Array<String>): Int
|
||||
|
||||
private external fun jniCreateSocket(
|
||||
ip: String,
|
||||
port: Int,
|
||||
@ -79,6 +89,9 @@ class ByeDpiProxy {
|
||||
defaultTtl: Int,
|
||||
customTtl: Boolean,
|
||||
noDomain: Boolean,
|
||||
desyncHttp: Boolean,
|
||||
desyncHttps: Boolean,
|
||||
desyncUdp: Boolean,
|
||||
desyncMethod: Int,
|
||||
splitPosition: Int,
|
||||
splitAtHost: Boolean,
|
||||
|
@ -1,14 +1,48 @@
|
||||
package io.github.dovecoteescapee.byedpi.core
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import io.github.dovecoteescapee.byedpi.utility.getStringNotNull
|
||||
import io.github.dovecoteescapee.byedpi.utility.shellSplit
|
||||
|
||||
class ByeDpiProxyPreferences(
|
||||
sealed interface ByeDpiProxyPreferences {
|
||||
companion object {
|
||||
fun fromSharedPreferences(preferences: SharedPreferences): ByeDpiProxyPreferences =
|
||||
when (preferences.getBoolean("byedpi_enable_cmd_settings", false)) {
|
||||
true -> ByeDpiProxyCmdPreferences(preferences)
|
||||
false -> ByeDpiProxyUIPreferences(preferences)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ByeDpiProxyCmdPreferences(val args: Array<String>) : ByeDpiProxyPreferences {
|
||||
constructor(cmd: String) : this(cmdToArgs(cmd))
|
||||
|
||||
constructor(preferences: SharedPreferences) : this(
|
||||
preferences.getStringNotNull(
|
||||
"byedpi_cmd_args",
|
||||
""
|
||||
)
|
||||
)
|
||||
|
||||
companion object {
|
||||
private fun cmdToArgs(cmd: String): Array<String> {
|
||||
val firstArgIndex = cmd.indexOf("-")
|
||||
val argsStr = (if (firstArgIndex > 0) cmd.substring(firstArgIndex) else cmd).trim()
|
||||
return arrayOf("ciadpi") + shellSplit(argsStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ByeDpiProxyUIPreferences(
|
||||
ip: String? = null,
|
||||
port: Int? = null,
|
||||
maxConnections: Int? = null,
|
||||
bufferSize: Int? = null,
|
||||
defaultTtl: Int? = null,
|
||||
noDomain: Boolean? = null,
|
||||
desyncHttp: Boolean? = null,
|
||||
desyncHttps: Boolean? = null,
|
||||
desyncUdp: Boolean? = null,
|
||||
desyncMethod: DesyncMethod? = null,
|
||||
splitPosition: Int? = null,
|
||||
splitAtHost: Boolean? = null,
|
||||
@ -21,7 +55,7 @@ class ByeDpiProxyPreferences(
|
||||
tlsRecordSplit: Boolean? = null,
|
||||
tlsRecordSplitPosition: Int? = null,
|
||||
tlsRecordSplitAtSni: Boolean? = null,
|
||||
) {
|
||||
) : ByeDpiProxyPreferences {
|
||||
val ip: String = ip ?: "127.0.0.1"
|
||||
val port: Int = port ?: 1080
|
||||
val maxConnections: Int = maxConnections ?: 512
|
||||
@ -29,6 +63,9 @@ class ByeDpiProxyPreferences(
|
||||
val defaultTtl: Int = defaultTtl ?: 0
|
||||
val customTtl: Boolean = defaultTtl != null
|
||||
val noDomain: Boolean = noDomain ?: false
|
||||
val desyncHttp: Boolean = desyncHttp ?: true
|
||||
val desyncHttps: Boolean = desyncHttps ?: true
|
||||
val desyncUdp: Boolean = desyncUdp ?: false
|
||||
val desyncMethod: DesyncMethod = desyncMethod ?: DesyncMethod.Disorder
|
||||
val splitPosition: Int = splitPosition ?: 3
|
||||
val splitAtHost: Boolean = splitAtHost ?: false
|
||||
@ -49,6 +86,9 @@ class ByeDpiProxyPreferences(
|
||||
bufferSize = preferences.getString("byedpi_buffer_size", null)?.toIntOrNull(),
|
||||
defaultTtl = preferences.getString("byedpi_default_ttl", null)?.toIntOrNull(),
|
||||
noDomain = preferences.getBoolean("byedpi_no_domain", false),
|
||||
desyncHttp = preferences.getBoolean("byedpi_desync_http", true),
|
||||
desyncHttps = preferences.getBoolean("byedpi_desync_https", true),
|
||||
desyncUdp = preferences.getBoolean("byedpi_desync_udp", false),
|
||||
desyncMethod = preferences.getString("byedpi_desync_method", null)
|
||||
?.let { DesyncMethod.fromName(it) },
|
||||
splitPosition = preferences.getString("byedpi_split_position", null)?.toIntOrNull(),
|
||||
@ -60,9 +100,10 @@ class ByeDpiProxyPreferences(
|
||||
domainMixedCase = preferences.getBoolean("byedpi_domain_mixed_case", false),
|
||||
hostRemoveSpaces = preferences.getBoolean("byedpi_host_remove_spaces", false),
|
||||
tlsRecordSplit = preferences.getBoolean("byedpi_tlsrec_enabled", false),
|
||||
tlsRecordSplitPosition = preferences.getString("byedpi_tlsrec_position", null)?.toIntOrNull(),
|
||||
tlsRecordSplitPosition = preferences.getString("byedpi_tlsrec_position", null)
|
||||
?.toIntOrNull(),
|
||||
tlsRecordSplitAtSni = preferences.getBoolean("byedpi_tlsrec_at_sni", false),
|
||||
)
|
||||
)
|
||||
|
||||
enum class DesyncMethod {
|
||||
None,
|
||||
|
@ -0,0 +1,11 @@
|
||||
package io.github.dovecoteescapee.byedpi.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
|
||||
class ByeDpiCommandLineSettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.byedpi_cmd_settings, rootKey)
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
package io.github.dovecoteescapee.byedpi.fragments
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.preference.*
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences
|
||||
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyUIPreferences.DesyncMethod.*
|
||||
import io.github.dovecoteescapee.byedpi.utility.*
|
||||
|
||||
class ByeDpiUISettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val preferenceListener =
|
||||
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
|
||||
updatePreferences()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.byedpi_ui_settings, rootKey)
|
||||
|
||||
setEditTextPreferenceListener("byedpi_proxy_ip") { checkIp(it) }
|
||||
setEditTestPreferenceListenerPort("byedpi_proxy_port")
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_max_connections",
|
||||
1,
|
||||
Short.MAX_VALUE.toInt()
|
||||
)
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_buffer_size",
|
||||
1,
|
||||
Int.MAX_VALUE / 4
|
||||
)
|
||||
setEditTestPreferenceListenerInt("byedpi_default_ttl", 0, 255)
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_split_position",
|
||||
Int.MIN_VALUE,
|
||||
Int.MAX_VALUE
|
||||
)
|
||||
setEditTestPreferenceListenerInt("byedpi_fake_ttl", 1, 255)
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_tlsrec_position",
|
||||
2 * Short.MIN_VALUE,
|
||||
2 * Short.MAX_VALUE,
|
||||
)
|
||||
|
||||
findPreferenceNotNull<EditTextPreference>("byedpi_oob_data")
|
||||
.setOnBindEditTextListener {
|
||||
it.filters = arrayOf(android.text.InputFilter.LengthFilter(1))
|
||||
}
|
||||
|
||||
updatePreferences()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
sharedPreferences?.registerOnSharedPreferenceChangeListener(preferenceListener)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
sharedPreferences?.unregisterOnSharedPreferenceChangeListener(preferenceListener)
|
||||
}
|
||||
|
||||
private fun updatePreferences() {
|
||||
val desyncMethod =
|
||||
findPreferenceNotNull<ListPreference>("byedpi_desync_method")
|
||||
.value.let { ByeDpiProxyUIPreferences.DesyncMethod.fromName(it) }
|
||||
|
||||
val desyncHttp = findPreferenceNotNull<CheckBoxPreference>("byedpi_desync_http")
|
||||
val desyncHttps = findPreferenceNotNull<CheckBoxPreference>("byedpi_desync_https")
|
||||
val desyncUdp = findPreferenceNotNull<CheckBoxPreference>("byedpi_desync_udp")
|
||||
val splitPosition = findPreferenceNotNull<EditTextPreference>("byedpi_split_position")
|
||||
val splitAtHost = findPreferenceNotNull<CheckBoxPreference>("byedpi_split_at_host")
|
||||
val ttlFake = findPreferenceNotNull<EditTextPreference>("byedpi_fake_ttl")
|
||||
val fakeSni = findPreferenceNotNull<EditTextPreference>("byedpi_fake_sni")
|
||||
val oobData = findPreferenceNotNull<EditTextPreference>("byedpi_oob_data")
|
||||
val hostMixedCase = findPreferenceNotNull<CheckBoxPreference>("byedpi_host_mixed_case")
|
||||
val domainMixedCase = findPreferenceNotNull<CheckBoxPreference>("byedpi_domain_mixed_case")
|
||||
val hostRemoveSpaces =
|
||||
findPreferenceNotNull<CheckBoxPreference>("byedpi_host_remove_spaces")
|
||||
val splitTlsRec = findPreferenceNotNull<CheckBoxPreference>("byedpi_tlsrec_enabled")
|
||||
val splitTlsRecPosition =
|
||||
findPreferenceNotNull<EditTextPreference>("byedpi_tlsrec_position")
|
||||
val splitTlsRecAtSni = findPreferenceNotNull<CheckBoxPreference>("byedpi_tlsrec_at_sni")
|
||||
|
||||
when (desyncMethod) {
|
||||
None -> {
|
||||
desyncHttp.isVisible = false
|
||||
desyncHttps.isVisible = false
|
||||
desyncUdp.isVisible = false
|
||||
splitPosition.isVisible = false
|
||||
splitAtHost.isVisible = false
|
||||
ttlFake.isVisible = false
|
||||
fakeSni.isVisible = false
|
||||
oobData.isVisible = false
|
||||
hostMixedCase.isVisible = false
|
||||
domainMixedCase.isVisible = false
|
||||
hostRemoveSpaces.isVisible = false
|
||||
}
|
||||
|
||||
else -> {
|
||||
desyncHttp.isVisible = true
|
||||
desyncHttps.isVisible = true
|
||||
desyncUdp.isVisible = true
|
||||
splitPosition.isVisible = true
|
||||
splitAtHost.isVisible = true
|
||||
|
||||
val desyncAllProtocols =
|
||||
!desyncHttp.isChecked && !desyncHttps.isChecked && !desyncUdp.isChecked
|
||||
|
||||
if (desyncAllProtocols || desyncHttp.isChecked) {
|
||||
hostMixedCase.isVisible = true
|
||||
domainMixedCase.isVisible = true
|
||||
hostRemoveSpaces.isVisible = true
|
||||
} else {
|
||||
hostMixedCase.isVisible = false
|
||||
domainMixedCase.isVisible = false
|
||||
hostRemoveSpaces.isVisible = false
|
||||
}
|
||||
|
||||
when (desyncMethod) {
|
||||
Fake -> {
|
||||
ttlFake.isVisible = true
|
||||
fakeSni.isVisible = true
|
||||
oobData.isVisible = false
|
||||
}
|
||||
|
||||
OOB -> {
|
||||
ttlFake.isVisible = false
|
||||
fakeSni.isVisible = false
|
||||
oobData.isVisible = true
|
||||
}
|
||||
|
||||
else -> {
|
||||
ttlFake.isVisible = false
|
||||
fakeSni.isVisible = false
|
||||
oobData.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
splitTlsRecPosition.isVisible = splitTlsRec.isChecked
|
||||
splitTlsRecAtSni.isVisible = splitTlsRec.isChecked
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package io.github.dovecoteescapee.byedpi.fragments
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.*
|
||||
import io.github.dovecoteescapee.byedpi.BuildConfig
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.data.Mode
|
||||
import io.github.dovecoteescapee.byedpi.utility.*
|
||||
|
||||
class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||
companion object {
|
||||
private val TAG: String = MainSettingsFragment::class.java.simpleName
|
||||
|
||||
fun setTheme(name: String) =
|
||||
themeByName(name)?.let {
|
||||
AppCompatDelegate.setDefaultNightMode(it)
|
||||
} ?: throw IllegalStateException("Invalid value for app_theme: $name")
|
||||
|
||||
private fun themeByName(name: String): Int? = when (name) {
|
||||
"system" -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
"light" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
else -> {
|
||||
Log.w(TAG, "Invalid value for app_theme: $name")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val preferenceListener =
|
||||
SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
|
||||
updatePreferences()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.main_settings, rootKey)
|
||||
|
||||
setEditTextPreferenceListener("dns_ip") { checkIp(it) }
|
||||
|
||||
findPreferenceNotNull<DropDownPreference>("app_theme")
|
||||
.setOnPreferenceChangeListener { _, newValue ->
|
||||
setTheme(newValue as String)
|
||||
true
|
||||
}
|
||||
|
||||
val switchCommandLineSettings = findPreferenceNotNull<SwitchPreference>(
|
||||
"byedpi_enable_cmd_settings"
|
||||
)
|
||||
val uiSettings = findPreferenceNotNull<Preference>("byedpi_ui_settings")
|
||||
val cmdSettings = findPreferenceNotNull<Preference>("byedpi_cmd_settings")
|
||||
|
||||
val setByeDpiSettingsMode = { enable: Boolean ->
|
||||
uiSettings.isEnabled = !enable
|
||||
cmdSettings.isEnabled = enable
|
||||
}
|
||||
|
||||
setByeDpiSettingsMode(switchCommandLineSettings.isChecked)
|
||||
|
||||
switchCommandLineSettings.setOnPreferenceChangeListener { _, newValue ->
|
||||
setByeDpiSettingsMode(newValue as Boolean)
|
||||
true
|
||||
}
|
||||
|
||||
findPreferenceNotNull<Preference>("version").summary = BuildConfig.VERSION_NAME
|
||||
|
||||
updatePreferences()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
sharedPreferences?.registerOnSharedPreferenceChangeListener(preferenceListener)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
sharedPreferences?.unregisterOnSharedPreferenceChangeListener(preferenceListener)
|
||||
}
|
||||
|
||||
private fun updatePreferences() {
|
||||
val mode = findPreferenceNotNull<ListPreference>("byedpi_mode")
|
||||
.value.let { Mode.fromString(it) }
|
||||
val dns = findPreferenceNotNull<EditTextPreference>("dns_ip")
|
||||
|
||||
when (mode) {
|
||||
Mode.VPN -> dns.isVisible = true
|
||||
Mode.Proxy -> dns.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
package io.github.dovecoteescapee.byedpi.fragments
|
||||
|
||||
import android.net.InetAddresses
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.preference.DropDownPreference
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import io.github.dovecoteescapee.byedpi.BuildConfig
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
companion object {
|
||||
private val TAG: String = SettingsFragment::class.java.simpleName
|
||||
|
||||
fun setTheme(name: String): Boolean = when (val theme = themeByName(name)) {
|
||||
null -> {
|
||||
Log.w(TAG, "Invalid value for app_theme: $name")
|
||||
false
|
||||
}
|
||||
|
||||
else -> {
|
||||
AppCompatDelegate.setDefaultNightMode(theme)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun themeByName(name: String): Int? = when (name) {
|
||||
"system" -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
"light" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
"dark" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
else -> {
|
||||
Log.w(TAG, "Invalid value for app_theme: $name")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIp(ip: String): Boolean =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
InetAddresses.isNumericAddress(ip)
|
||||
} else {
|
||||
// This pattern doesn't not support IPv6
|
||||
Patterns.IP_ADDRESS.matcher(ip).matches()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||
|
||||
findPreference<DropDownPreference>("app_theme")
|
||||
?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
setTheme(newValue as String)
|
||||
}
|
||||
|
||||
setEditTextPreferenceListener("dns_ip") { checkIp(it) }
|
||||
setEditTextPreferenceListener("byedpi_proxy_ip") { checkIp(it) }
|
||||
setEditTestPreferenceListenerPort("byedpi_proxy_port")
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_max_connections",
|
||||
1,
|
||||
Short.MAX_VALUE.toInt()
|
||||
)
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_buffer_size",
|
||||
1,
|
||||
Int.MAX_VALUE / 4
|
||||
)
|
||||
setEditTestPreferenceListenerInt("byedpi_default_ttl", 0, 255)
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_split_position",
|
||||
Int.MIN_VALUE,
|
||||
Int.MAX_VALUE
|
||||
)
|
||||
setEditTestPreferenceListenerInt("byedpi_fake_ttl", 1, 255)
|
||||
setEditTestPreferenceListenerInt(
|
||||
"byedpi_tlsrec_position",
|
||||
2 * Short.MIN_VALUE,
|
||||
2 * Short.MAX_VALUE,
|
||||
)
|
||||
|
||||
findPreference<Preference>("version")?.summary =
|
||||
BuildConfig.VERSION_NAME
|
||||
}
|
||||
|
||||
private fun setEditTestPreferenceListenerPort(key: String) {
|
||||
setEditTestPreferenceListenerInt(key, 1, 65535)
|
||||
}
|
||||
|
||||
private fun setEditTestPreferenceListenerInt(
|
||||
key: String,
|
||||
min: Int = Int.MIN_VALUE,
|
||||
max: Int = Int.MAX_VALUE
|
||||
) {
|
||||
setEditTextPreferenceListener(key) { value ->
|
||||
value.toIntOrNull()?.let { it in min..max } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
private fun setEditTextPreferenceListener(key: String, check: (String) -> Boolean) {
|
||||
findPreference<EditTextPreference>(key)
|
||||
?.setOnPreferenceChangeListener { preference, newValue ->
|
||||
when (newValue) {
|
||||
is String -> {
|
||||
val valid = check(newValue)
|
||||
if (!valid) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Invalid value for ${preference.title}: $newValue",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
valid
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Invalid type for ${preference.key}: " +
|
||||
"$newValue has type ${newValue::class.java}"
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,19 +10,8 @@ import androidx.lifecycle.lifecycleScope
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy
|
||||
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyPreferences
|
||||
import io.github.dovecoteescapee.byedpi.data.AppStatus
|
||||
import io.github.dovecoteescapee.byedpi.data.START_ACTION
|
||||
import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
|
||||
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Mode
|
||||
import io.github.dovecoteescapee.byedpi.data.SENDER
|
||||
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Sender
|
||||
import io.github.dovecoteescapee.byedpi.data.ServiceStatus
|
||||
import io.github.dovecoteescapee.byedpi.utility.createConnectionNotification
|
||||
import io.github.dovecoteescapee.byedpi.utility.getPreferences
|
||||
import io.github.dovecoteescapee.byedpi.utility.registerNotificationChannel
|
||||
import io.github.dovecoteescapee.byedpi.data.*
|
||||
import io.github.dovecoteescapee.byedpi.utility.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@ -159,7 +148,7 @@ class ByeDpiProxyService : LifecycleService() {
|
||||
}
|
||||
|
||||
private fun getByeDpiPreferences(): ByeDpiProxyPreferences =
|
||||
ByeDpiProxyPreferences(getPreferences(this))
|
||||
ByeDpiProxyPreferences.fromSharedPreferences(getPreferences())
|
||||
|
||||
private fun updateStatus(newStatus: ServiceStatus) {
|
||||
Log.d(TAG, "Proxy status changed from $status to $newStatus")
|
||||
|
@ -15,19 +15,8 @@ import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.activities.MainActivity
|
||||
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxy
|
||||
import io.github.dovecoteescapee.byedpi.core.ByeDpiProxyPreferences
|
||||
import io.github.dovecoteescapee.byedpi.data.AppStatus
|
||||
import io.github.dovecoteescapee.byedpi.data.START_ACTION
|
||||
import io.github.dovecoteescapee.byedpi.data.STOP_ACTION
|
||||
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Mode
|
||||
import io.github.dovecoteescapee.byedpi.data.SENDER
|
||||
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Sender
|
||||
import io.github.dovecoteescapee.byedpi.data.ServiceStatus
|
||||
import io.github.dovecoteescapee.byedpi.utility.createConnectionNotification
|
||||
import io.github.dovecoteescapee.byedpi.utility.getPreferences
|
||||
import io.github.dovecoteescapee.byedpi.utility.registerNotificationChannel
|
||||
import io.github.dovecoteescapee.byedpi.data.*
|
||||
import io.github.dovecoteescapee.byedpi.utility.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@ -189,9 +178,9 @@ class ByeDpiVpnService : LifecycleVpnService() {
|
||||
throw IllegalStateException("VPN field not null")
|
||||
}
|
||||
|
||||
val sharedPreferences = getPreferences(this)
|
||||
val sharedPreferences = getPreferences()
|
||||
val port = sharedPreferences.getString("byedpi_proxy_port", null)?.toInt() ?: 1080
|
||||
val dns = sharedPreferences.getString("dns_ip", null) ?: "1.1.1.1"
|
||||
val dns = sharedPreferences.getStringNotNull("dns_ip", "1.1.1.1")
|
||||
|
||||
val vpn = createBuilder(dns).establish()
|
||||
?: throw IllegalStateException("VPN connection failed")
|
||||
@ -213,7 +202,7 @@ class ByeDpiVpnService : LifecycleVpnService() {
|
||||
}
|
||||
|
||||
private fun getByeDpiPreferences(): ByeDpiProxyPreferences =
|
||||
ByeDpiProxyPreferences(getPreferences(this))
|
||||
ByeDpiProxyPreferences.fromSharedPreferences(getPreferences())
|
||||
|
||||
private fun updateStatus(newStatus: ServiceStatus) {
|
||||
Log.d(TAG, "VPN status changed from $status to $newStatus")
|
||||
|
@ -8,7 +8,11 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ServiceLifecycleDispatcher
|
||||
|
||||
/**
|
||||
* Based on @link [androidx.lifecycle.LifecycleService]
|
||||
*/
|
||||
open class LifecycleVpnService : VpnService(), LifecycleOwner {
|
||||
@Suppress("LeakingThis")
|
||||
private val dispatcher = ServiceLifecycleDispatcher(this)
|
||||
|
||||
@CallSuper
|
||||
@ -48,4 +52,4 @@ open class LifecycleVpnService : VpnService(), LifecycleOwner {
|
||||
|
||||
override val lifecycle: Lifecycle
|
||||
get() = dispatcher.lifecycle
|
||||
}
|
||||
}
|
||||
|
@ -17,13 +17,7 @@ import androidx.core.service.quicksettings.PendingIntentActivityWrapper
|
||||
import androidx.core.service.quicksettings.TileServiceCompat
|
||||
import io.github.dovecoteescapee.byedpi.R
|
||||
import io.github.dovecoteescapee.byedpi.activities.MainActivity
|
||||
import io.github.dovecoteescapee.byedpi.data.AppStatus
|
||||
import io.github.dovecoteescapee.byedpi.data.FAILED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Mode
|
||||
import io.github.dovecoteescapee.byedpi.data.SENDER
|
||||
import io.github.dovecoteescapee.byedpi.data.STARTED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.STOPPED_BROADCAST
|
||||
import io.github.dovecoteescapee.byedpi.data.Sender
|
||||
import io.github.dovecoteescapee.byedpi.data.*
|
||||
import io.github.dovecoteescapee.byedpi.utility.getPreferences
|
||||
import io.github.dovecoteescapee.byedpi.utility.mode
|
||||
|
||||
@ -116,7 +110,7 @@ class QuickTileService : TileService() {
|
||||
val (status) = appStatus
|
||||
when (status) {
|
||||
AppStatus.Halted -> {
|
||||
val mode = getPreferences(this).mode()
|
||||
val mode = getPreferences().mode()
|
||||
|
||||
if (mode == Mode.VPN && VpnService.prepare(this) != null) {
|
||||
updateStatus()
|
||||
@ -130,4 +124,4 @@ class QuickTileService : TileService() {
|
||||
AppStatus.Running -> ServiceManager.stop(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,4 +47,4 @@ object ServiceManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package io.github.dovecoteescapee.byedpi.utility
|
||||
|
||||
// Based on https://gist.github.com/raymyers/8077031
|
||||
fun shellSplit(string: CharSequence): List<String> {
|
||||
val tokens: MutableList<String> = ArrayList()
|
||||
var escaping = false
|
||||
var quoteChar = ' '
|
||||
var quoting = false
|
||||
var lastCloseQuoteIndex = Int.MIN_VALUE
|
||||
var current = StringBuilder()
|
||||
|
||||
for (i in string.indices) {
|
||||
val c = string[i]
|
||||
|
||||
if (escaping) {
|
||||
current.append(c)
|
||||
escaping = false
|
||||
} else if (c == '\\' && !(quoting && quoteChar == '\'')) {
|
||||
escaping = true
|
||||
} else if (quoting && c == quoteChar) {
|
||||
quoting = false
|
||||
lastCloseQuoteIndex = i
|
||||
} else if (!quoting && (c == '\'' || c == '"')) {
|
||||
quoting = true
|
||||
quoteChar = c
|
||||
} else if (!quoting && Character.isWhitespace(c)) {
|
||||
if (current.isNotEmpty() || lastCloseQuoteIndex == i - 1) {
|
||||
tokens.add(current.toString())
|
||||
current = StringBuilder()
|
||||
}
|
||||
} else {
|
||||
current.append(c)
|
||||
}
|
||||
}
|
||||
|
||||
if (current.isNotEmpty() || lastCloseQuoteIndex == string.length - 1) {
|
||||
tokens.add(current.toString())
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
@ -2,14 +2,22 @@ package io.github.dovecoteescapee.byedpi.utility
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import io.github.dovecoteescapee.byedpi.data.Mode
|
||||
|
||||
fun getPreferences(context: Context): SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val PreferenceFragmentCompat.sharedPreferences
|
||||
get() = preferenceScreen.sharedPreferences
|
||||
|
||||
fun Context.getPreferences(): SharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
fun SharedPreferences.getStringNotNull(key: String, defValue: String): String =
|
||||
getString(key, defValue) ?: defValue
|
||||
|
||||
fun SharedPreferences.mode(): Mode =
|
||||
Mode.fromString(getStringNotNull("byedpi_mode", "vpn"))
|
||||
|
||||
fun <T : Preference> PreferenceFragmentCompat.findPreferenceNotNull(key: CharSequence): T =
|
||||
findPreference(key) ?: throw IllegalStateException("Preference $key not found")
|
||||
|
@ -0,0 +1,65 @@
|
||||
package io.github.dovecoteescapee.byedpi.utility
|
||||
|
||||
import android.net.InetAddresses
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
|
||||
private const val TAG = "ValidateUtils"
|
||||
|
||||
fun checkIp(ip: String): Boolean =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
InetAddresses.isNumericAddress(ip)
|
||||
} else {
|
||||
// This pattern doesn't not support IPv6
|
||||
@Suppress("DEPRECATION")
|
||||
Patterns.IP_ADDRESS.matcher(ip).matches()
|
||||
}
|
||||
|
||||
fun PreferenceFragmentCompat.setEditTestPreferenceListenerPort(key: String) {
|
||||
setEditTestPreferenceListenerInt(key, 1, 65535)
|
||||
}
|
||||
|
||||
fun PreferenceFragmentCompat.setEditTestPreferenceListenerInt(
|
||||
key: String,
|
||||
min: Int = Int.MIN_VALUE,
|
||||
max: Int = Int.MAX_VALUE
|
||||
) {
|
||||
setEditTextPreferenceListener(key) { value ->
|
||||
value.toIntOrNull()?.let { it in min..max } ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun PreferenceFragmentCompat.setEditTextPreferenceListener(
|
||||
key: String,
|
||||
check: (String) -> Boolean
|
||||
) {
|
||||
findPreferenceNotNull<EditTextPreference>(key)
|
||||
.setOnPreferenceChangeListener { preference, newValue ->
|
||||
when (newValue) {
|
||||
is String -> {
|
||||
val valid = check(newValue)
|
||||
if (!valid) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Invalid value for ${preference.title}: $newValue",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
valid
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Invalid type for ${preference.key}: " +
|
||||
"$newValue has type ${newValue::class.java}"
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -47,4 +47,14 @@
|
||||
<string name="general_category">General</string>
|
||||
<string name="byedpi_category">ByeDPI</string>
|
||||
<string name="about_category">About</string>
|
||||
<string name="command_line_editor">Command line editor</string>
|
||||
<string name="ui_editor">UI editor</string>
|
||||
<string name="use_command_line_settings">Use command line settings</string>
|
||||
<string name="desync_http">Desync HTTP</string>
|
||||
<string name="desync_https">Desync HTTPS</string>
|
||||
<string name="desync_udp">Desync UDP</string>
|
||||
<string name="oob_data">OOB Data</string>
|
||||
<string name="sni_of_fake_packet">SNI of fake packet</string>
|
||||
<string name="byedpi_proxy">Proxy</string>
|
||||
<string name="byedpi_desync">Desync</string>
|
||||
</resources>
|
25
app/src/main/res/xml/byedpi_cmd_settings.xml
Normal file
25
app/src/main/res/xml/byedpi_cmd_settings.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tag="byedpi_cmd_settings">
|
||||
|
||||
<Preference
|
||||
android:key="byedpi_readme"
|
||||
android:title="Documentation"
|
||||
android:textColor="?attr/textFillColor"
|
||||
android:icon="@drawable/ic_github_36">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="https://github.com/hufrea/byedpi/tree/main#readme-ov-file" />
|
||||
</Preference>
|
||||
|
||||
<androidx.preference.PreferenceCategory>
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="byedpi_cmd_args"
|
||||
android:title="Command line arguments"
|
||||
android:dialogTitle="Command line arguments"
|
||||
android:inputType="textMultiLine" />
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
@ -1,48 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:tag="settings_screen">
|
||||
android:tag="byedpi_ui_settings">
|
||||
|
||||
<Preference
|
||||
android:key="byedpi_readme"
|
||||
android:title="@string/byedpi_readme_link"
|
||||
android:textColor="?attr/textFillColor"
|
||||
android:icon="@drawable/ic_github_36">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="https://github.com/hufrea/byedpi/tree/main#readme-ov-file" />
|
||||
</Preference>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/general_category">
|
||||
|
||||
<DropDownPreference
|
||||
android:key="app_theme"
|
||||
android:title="@string/theme_settings"
|
||||
android:entries="@array/themes"
|
||||
android:entryValues="@array/themes_entries"
|
||||
android:defaultValue="system"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<DropDownPreference
|
||||
android:key="byedpi_mode"
|
||||
android:title="@string/mode_setting"
|
||||
android:entries="@array/byedpi_modes"
|
||||
android:entryValues="@array/byedpi_modes_entries"
|
||||
android:defaultValue="vpn"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/byedpi_category">
|
||||
|
||||
<Preference
|
||||
android:key="byedpi_readme"
|
||||
android:title="@string/byedpi_readme_link"
|
||||
android:textColor="?attr/textFillColor"
|
||||
android:icon="@drawable/ic_github_36">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="https://github.com/hufrea/byedpi/tree/main#readme-ov-file" />
|
||||
</Preference>
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="dns_ip"
|
||||
android:title="@string/dbs_ip_setting"
|
||||
android:defaultValue="1.1.1.1"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
android:title="@string/byedpi_proxy">
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="byedpi_proxy_ip"
|
||||
@ -71,6 +43,16 @@
|
||||
android:defaultValue="16384"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="byedpi_no_domain"
|
||||
android:title="@string/byedpi_no_domain_setting"
|
||||
android:defaultValue="false" />
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/byedpi_desync">
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="byedpi_default_ttl"
|
||||
android:title="@string/byedpi_default_ttl_setting"
|
||||
@ -78,11 +60,6 @@
|
||||
android:defaultValue="0"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="byedpi_no_domain"
|
||||
android:title="@string/byedpi_no_domain_setting"
|
||||
android:defaultValue="false"/>
|
||||
|
||||
<DropDownPreference
|
||||
android:key="byedpi_desync_method"
|
||||
android:title="@string/byedpi_desync_method_setting"
|
||||
@ -91,6 +68,21 @@
|
||||
android:defaultValue="disorder"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="byedpi_desync_http"
|
||||
android:title="@string/desync_http"
|
||||
android:defaultValue="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="byedpi_desync_https"
|
||||
android:title="@string/desync_https"
|
||||
android:defaultValue="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="byedpi_desync_udp"
|
||||
android:title="@string/desync_udp"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="byedpi_split_position"
|
||||
android:title="@string/byedpi_split_position_setting"
|
||||
@ -112,13 +104,13 @@
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="byedpi_fake_sni"
|
||||
android:title="SNI of fake packet"
|
||||
android:title="@string/sni_of_fake_packet"
|
||||
android:defaultValue="www.iana.org"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="byedpi_oob_data"
|
||||
android:title="OOB Data"
|
||||
android:title="@string/oob_data"
|
||||
android:defaultValue="a"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
@ -156,24 +148,4 @@
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/about_category">
|
||||
|
||||
<Preference
|
||||
android:key="version"
|
||||
android:title="@string/version"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
tools:summary="1.0.0" />
|
||||
|
||||
<Preference
|
||||
android:key="source_code"
|
||||
android:title="@string/source_code_link"
|
||||
android:icon="@drawable/ic_github_36">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="https://github.com/dovecoteescapee/ByeDPIAndroid" />
|
||||
</Preference>
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
76
app/src/main/res/xml/main_settings.xml
Normal file
76
app/src/main/res/xml/main_settings.xml
Normal file
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:tag="main_settings">
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/general_category">
|
||||
|
||||
<DropDownPreference
|
||||
android:key="app_theme"
|
||||
android:title="@string/theme_settings"
|
||||
android:entries="@array/themes"
|
||||
android:entryValues="@array/themes_entries"
|
||||
android:defaultValue="system"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<DropDownPreference
|
||||
android:key="byedpi_mode"
|
||||
android:title="@string/mode_setting"
|
||||
android:entries="@array/byedpi_modes"
|
||||
android:entryValues="@array/byedpi_modes_entries"
|
||||
android:defaultValue="vpn"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<com.takisoft.preferencex.EditTextPreference
|
||||
android:key="dns_ip"
|
||||
android:title="@string/dbs_ip_setting"
|
||||
android:defaultValue="1.1.1.1"
|
||||
app:useSimpleSummaryProvider="true"/>
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/byedpi_category">
|
||||
|
||||
<SwitchPreference
|
||||
android:key="byedpi_enable_cmd_settings"
|
||||
android:title="@string/use_command_line_settings"
|
||||
android:defaultValue="false" />
|
||||
|
||||
<Preference
|
||||
android:key="byedpi_ui_settings"
|
||||
android:title="@string/ui_editor"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:fragment="io.github.dovecoteescapee.byedpi.fragments.ByeDpiUISettingsFragment" />
|
||||
|
||||
<Preference
|
||||
android:key="byedpi_cmd_settings"
|
||||
android:title="@string/command_line_editor"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
app:fragment="io.github.dovecoteescapee.byedpi.fragments.ByeDpiCommandLineSettingsFragment" />
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
<androidx.preference.PreferenceCategory
|
||||
android:title="@string/about_category">
|
||||
|
||||
<Preference
|
||||
android:key="version"
|
||||
android:title="@string/version"
|
||||
app:useSimpleSummaryProvider="true"
|
||||
tools:summary="1.0.0" />
|
||||
|
||||
<Preference
|
||||
android:key="source_code"
|
||||
android:title="@string/source_code_link"
|
||||
android:icon="@drawable/ic_github_36">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:data="https://github.com/dovecoteescapee/ByeDPIAndroid" />
|
||||
</Preference>
|
||||
|
||||
</androidx.preference.PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user