Compare commits

...

40 Commits

Author SHA1 Message Date
2dust
3548dcbb67 Merge pull request #1065 from yuhan6665/domestic
Always add domestic dns to config
2021-05-24 08:14:43 +08:00
yuhan6665
b897b2a0e9 Always add domestic dns to config
Previously, domestic dns is only added to config under
"local dns mode". However, it should be used for V2ray
core routing DNS as well.
2021-05-23 11:56:23 -04:00
2dust
eb60e4a0e4 Merge pull request #1058 from yuhan6665/grpc
Grpc
2021-05-16 19:35:16 +08:00
yuhan6665
c3dfa8cedc Support gRPC import and export
New Format https://github.com/XTLS/Xray-core/issues/91
For Vmess QRcode https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
gRPC mode is mapped to "type"
2021-05-15 23:18:09 -04:00
yuhan6665
f58bf85b6d Update UI for gRPC
Separate transport header types for different networks
Change UI dynamically based on user selection of network
2021-05-15 23:04:58 -04:00
2dust
906d0714b5 Merge pull request #1050 from yuhan6665/fakedns
Fakedns
2021-05-08 11:28:42 +08:00
yuhan6665
701fed2525 Update settings UI, add new VPN section 2021-05-07 21:55:44 -04:00
yuhan6665
e83208465f Add vpn dns preference setting
Vpn Dns can now be changed separately from remote Dns
2021-05-07 21:17:11 -04:00
yuhan6665
2b031033d3 Add fake dns preference setting 2021-05-07 20:55:02 -04:00
2dust
e0e16b5934 fix tcp http request 2021-05-07 20:50:52 -04:00
2dust
0748f994ef Merge pull request #1038 from yuhan6665/new-storage-fix
New storage fix
2021-05-01 18:20:11 +08:00
yuhan6665
233b34bda6 Fix scrolling by cache server config in ViewModel 2021-04-30 20:17:41 -04:00
yuhan6665
6ece3385fe Fix trojan sni saved as null when it should be empty 2021-04-30 20:17:41 -04:00
2dust
9b8a810445 Merge pull request #1028 from yuhan6665/master
Update tun2socks
2021-04-26 08:22:47 +08:00
yuhan6665
f63242d147 Update tun2socks
60a66a363f
compiled with NDK 22.1.7171670
2021-04-25 19:56:14 -04:00
2dust
7024fabb82 Merge pull request #1016 from yuhan6665/new-storage-fix
Fix data migration sniff setting default to true
2021-04-17 10:24:19 +08:00
2dust
459f52fec6 Merge pull request #1015 from yuhan6665/xray
Update AndroidLibrayLite project to Xray
2021-04-17 10:24:10 +08:00
yuhan6665
90f2d33d97 Fix data migration sniff setting default to true 2021-04-16 20:33:59 -04:00
yuhan6665
115008a8a4 Update AndroidLibrayLite project to Xray 2021-04-16 09:17:58 -04:00
2dust
8cdc7fb3c9 Merge pull request #1001 from yuhan6665/new-storage-fix
Rollback parsed custom config
2021-04-11 19:28:47 +08:00
yuhan6665
d0a2fa0086 Rollback parsed custom config
To minimize change, the data structure for ServerConfig still stay the same
Add another table for server raw config
2021-04-10 21:30:34 -04:00
2dust
f6c54841d2 Merge pull request #990 from yuhan6665/new-storage-fix
New storage fix
2021-04-05 20:21:51 +08:00
yuhan6665
54fa356999 Add some new config for v2fly 4.37.0 2021-04-04 19:25:52 -04:00
2dust
9642b7f64f up grpc 2021-04-04 19:25:52 -04:00
yuhan6665
dd2d2c1638 Add some missing config from Xray 2021-04-04 19:25:51 -04:00
yuhan6665
e21950dbcd Improve custom config error toast 2021-04-04 19:25:51 -04:00
2dust
d016ab06d4 Merge pull request #973 from yuhan6665/new-storage-fix
New storage fix
2021-03-29 08:06:43 +08:00
yuhan6665
4d9aced5a4 Gson display to not escape HTML character like "=" 2021-03-28 11:34:41 -04:00
yuhan6665
62b928e6a0 Support Trojan flow and email 2021-03-28 11:34:34 -04:00
2dust
0ce60eae73 Update MainRecyclerAdapter.kt 2021-03-28 11:34:12 -04:00
2dust
5930a6a9eb Update V2rayConfig.kt 2021-03-28 11:34:01 -04:00
2dust
a360310be2 fix header type none 2021-03-28 11:33:51 -04:00
2dust
820e6cdf36 fix migration inbound port parsed as 0 2021-03-28 11:31:18 -04:00
2dust
658b890325 Merge pull request #965 from yuhan6665/new-storage
New storage
2021-03-27 09:42:12 +08:00
yuhan6665
fb017c6659 Add migration
- Fix some compile warnings
- Add migration method
- Add vmessBean migration
2021-03-26 20:55:00 -04:00
yuhan6665
00e6314afe Merge branch 'master' into new-storage 2021-03-26 20:55:00 -04:00
yuhan6665
463f45804f Add missing data fields for config
Make custom config able to parse any config
Fix subscription TODO
Fix some bugs
- vless tls should clear out flow
- trojan tls settings
- import with null rawQuery link
- socks share link username
- remove an item in the middle of server list
2021-03-26 20:55:00 -04:00
yuhan6665
572955dd1e Move everything else to MMKV
- Exclude old armv5 so lib from MMKV
- Move subscription to MMKV
- Clean up settings key
- Map settings to MMKV
- Say goodbye to DPreference

Fix some bugs:
- null pointer check
- server list refresh
- show test result
- custom config display
- fix port number in DNS object shown as Double
- shadowsocks, socks streamsettings
- quic settings with wrong security
- main list footer
2021-03-20 01:00:12 -04:00
yuhan6665
375a209beb Move main storage references to MMKV
- Move current index to MMKV
- Move ServiceManager to MMKV
- Fix isRunning in ServerActivity
- Drop support for VMESS qrcode v1
- Change all protocols importing
2021-03-19 23:23:10 -04:00
yuhan6665
872f9ce199 Add MMKV and data class
- Move ServerActivity to MMKV
- Move ServerCustomConfigActivity to MMKV
- Cleanup ServerActivities
- Cleanup V2rayConfigUtil
- Change data reads in RecyclerAdapter and ConfigManager
- Cleanup some constants
2021-03-19 23:23:09 -04:00
76 changed files with 2716 additions and 3515 deletions

2
AndroidLibV2rayLite/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.jar
*.aar

View File

@@ -1,26 +0,0 @@
sudo: required
language: go
go:
- "1.12"
go_import_path: github.com/2dust/AndroidLibV2rayLite
git:
depth: 5
addons:
apt:
update: true
before_script:
- sudo ntpdate -u time.google.com
- date
- make all
- make downloadGoMobile
script:
- make BuildMobile
after_success:
deploy:
provider: releases
api_key: ${GH_TOKEN}
file:
- libv2ray.aar
skip_cleanup: true
on:
tags: true

View File

@@ -1,7 +1,7 @@
package CoreI package CoreI
import ( import (
v2core "v2ray.com/core" v2core "github.com/xtls/xray-core/core"
) )
type Status struct { type Status struct {
@@ -14,7 +14,7 @@ type Status struct {
} }
func CheckVersion() int { func CheckVersion() int {
return 22 return 23
} }
func (v *Status) GetDataDir() string { func (v *Status) GetDataDir() string {

View File

@@ -8,24 +8,19 @@ asset:
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat # cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat # cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat
fetchDep:
-go get github.com/2dust/AndroidLibV2rayLite
go get github.com/2dust/AndroidLibV2rayLite
ANDROID_HOME=$(HOME)/android-sdk-linux ANDROID_HOME=$(HOME)/android-sdk-linux
export ANDROID_HOME export ANDROID_HOME
PATH:=$(PATH):$(GOPATH)/bin PATH:=$(PATH):$(GOPATH)/bin
export PATH export PATH
downloadGoMobile: downloadGoMobile:
go get golang.org/x/mobile/cmd/...
sudo apt-get install -qq libstdc++6:i386 lib32z1 expect sudo apt-get install -qq libstdc++6:i386 lib32z1 expect
cd ~ ;curl -L https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/ubuntu-cli-install-android-sdk.sh | sudo bash - > /dev/null cd ~ ;curl -L https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/ubuntu-cli-install-android-sdk.sh | sudo bash - > /dev/null
ls ~ ls ~
ls ~/android-sdk-linux/ ls ~/android-sdk-linux/
gomobile init ;gomobile bind -v -tags json github.com/2dust/AndroidLibV2rayLite
BuildMobile: BuildMobile:
@echo Stub gomobile init
gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
all: asset pb fetchDep all: asset pb
@echo DONE @echo DONE

View File

@@ -12,8 +12,10 @@ import (
"time" "time"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
v2net "v2ray.com/core/common/net" v2net "github.com/xtls/xray-core/common/net"
v2internet "v2ray.com/core/transport/internet" "github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/outbound"
v2internet "github.com/xtls/xray-core/transport/internet"
) )
type protectSet interface { type protectSet interface {
@@ -190,6 +192,11 @@ func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
return return
} }
// Init implement internet.SystemDialer
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
// do nothing
}
// Dial exported as the protected dial method // Dial exported as the protected dial method
func (d *ProtectedDialer) Dial(ctx context.Context, func (d *ProtectedDialer) Dial(ctx context.Context,
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) { src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {

View File

@@ -9,7 +9,7 @@ import (
"testing" "testing"
"time" "time"
v2net "v2ray.com/core/common/net" v2net "github.com/xtls/xray-core/common/net"
) )
type fakeSupportSet struct{} type fakeSupportSet struct{}

4
AndroidLibV2rayLite/compile-tun2socks.sh Normal file → Executable file
View File

@@ -23,8 +23,8 @@ trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp
install -m644 $__dir/tun2socks.mk $TMPDIR/ install -m644 $__dir/tun2socks.mk $TMPDIR/
pushd $TMPDIR pushd $TMPDIR
git clone --depth=1 https://github.com/shadowsocks/badvpn.git git clone --depth=1 https://github.com/XTLS/badvpn.git
git clone --depth=1 https://github.com/shadowsocks/libancillary.git git clone --depth=1 https://github.com/XTLS/libancillary.git
$NDK_HOME/ndk-build \ $NDK_HOME/ndk-build \
NDK_PROJECT_PATH=. \ NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=./tun2socks.mk \ APP_BUILD_SCRIPT=./tun2socks.mk \

View File

@@ -0,0 +1,9 @@
module github.com/2dust/AndroidLibV2rayLite
go 1.16
require (
github.com/xtls/xray-core v1.4.2
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
)

303
AndroidLibV2rayLite/go.sum Normal file
View File

@@ -0,0 +1,303 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucas-clemente/quic-go v0.20.0/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
github.com/xtls/xray-core v1.4.2 h1:D0Le+Qy9L/eY5LbUQfrk7WJ8wbODpQSW/ZRCg+BRe7c=
github.com/xtls/xray-core v1.4.2/go.mod h1:DmL/9rOCliev/a6HciWEvSJVEhUF6C0EpD3clW8v0pc=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
h12.io/socks v1.0.2/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -13,18 +13,19 @@ import (
"github.com/2dust/AndroidLibV2rayLite/VPN" "github.com/2dust/AndroidLibV2rayLite/VPN"
mobasset "golang.org/x/mobile/asset" mobasset "golang.org/x/mobile/asset"
v2core "v2ray.com/core" v2core "github.com/xtls/xray-core/core"
v2filesystem "v2ray.com/core/common/platform/filesystem" v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
v2stats "v2ray.com/core/features/stats" v2stats "github.com/xtls/xray-core/features/stats"
v2serial "v2ray.com/core/infra/conf/serial" v2serial "github.com/xtls/xray-core/infra/conf/serial"
v2internet "v2ray.com/core/transport/internet" _ "github.com/xtls/xray-core/main/distro/all"
v2internet "github.com/xtls/xray-core/transport/internet"
v2applog "v2ray.com/core/app/log" v2applog "github.com/xtls/xray-core/app/log"
v2commlog "v2ray.com/core/common/log" v2commlog "github.com/xtls/xray-core/common/log"
) )
const ( const (
v2Assert = "v2ray.location.asset" v2Assert = "xray.location.asset"
assetperfix = "/dev/libv2rayfs0/asset" assetperfix = "/dev/libv2rayfs0/asset"
) )

View File

@@ -1,58 +0,0 @@
package libv2ray
import (
// The following are necessary as they register handlers in their init functions.
// Required features. Can't remove unless there is replacements.
_ "v2ray.com/core/app/dispatcher"
_ "v2ray.com/core/app/proxyman/inbound"
_ "v2ray.com/core/app/proxyman/outbound"
// Other optional features.
_ "v2ray.com/core/app/dns"
_ "v2ray.com/core/app/log"
_ "v2ray.com/core/app/policy"
_ "v2ray.com/core/app/router"
_ "v2ray.com/core/app/stats"
// Inbound and outbound proxies.
_ "v2ray.com/core/proxy/blackhole"
_ "v2ray.com/core/proxy/dns"
_ "v2ray.com/core/proxy/dokodemo"
_ "v2ray.com/core/proxy/freedom"
_ "v2ray.com/core/proxy/http"
_ "v2ray.com/core/proxy/mtproto"
_ "v2ray.com/core/proxy/shadowsocks"
_ "v2ray.com/core/proxy/socks"
_ "v2ray.com/core/proxy/trojan"
_ "v2ray.com/core/proxy/vless/inbound"
_ "v2ray.com/core/proxy/vless/outbound"
_ "v2ray.com/core/proxy/vmess/inbound"
_ "v2ray.com/core/proxy/vmess/outbound"
// Transport
_ "v2ray.com/core/transport/internet/http"
_ "v2ray.com/core/transport/internet/kcp"
_ "v2ray.com/core/transport/internet/quic"
_ "v2ray.com/core/transport/internet/tcp"
_ "v2ray.com/core/transport/internet/tls"
_ "v2ray.com/core/transport/internet/udp"
_ "v2ray.com/core/transport/internet/websocket"
// Transport headers
_ "v2ray.com/core/transport/internet/headers/http"
_ "v2ray.com/core/transport/internet/headers/noop"
_ "v2ray.com/core/transport/internet/headers/srtp"
_ "v2ray.com/core/transport/internet/headers/tls"
_ "v2ray.com/core/transport/internet/headers/utp"
_ "v2ray.com/core/transport/internet/headers/wechat"
_ "v2ray.com/core/transport/internet/headers/wireguard"
// JSON config support. Choose only one from the two below.
// The following line loads JSON from v2ctl
// _ "v2ray.com/core/main/json"
// The following line loads JSON internally
_ "v2ray.com/core/main/jsonem"
// Load config from file or http(s)
// _ "v2ray.com/core/main/confloader/external"
)

View File

@@ -7,7 +7,7 @@ import (
"log" "log"
"os" "os"
v2commlog "v2ray.com/core/common/log" v2commlog "github.com/xtls/xray-core/common/log"
) )
type consoleLogWriter struct { type consoleLogWriter struct {

View File

@@ -25,12 +25,14 @@ android {
minifyEnabled false minifyEnabled false
zipAlignEnabled false zipAlignEnabled false
shrinkResources false shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
debug { debug {
minifyEnabled false minifyEnabled false
zipAlignEnabled false zipAlignEnabled false
shrinkResources false shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
} }
} }
@@ -67,7 +69,6 @@ android {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13'
implementation project(':dpreference')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
@@ -89,6 +90,7 @@ dependencies {
implementation 'com.android.support.constraint:constraint-layout:2.0.1' implementation 'com.android.support.constraint:constraint-layout:2.0.1'
// DSL // DSL
implementation 'com.tencent:mmkv-static:1.0.19'
implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.google.code.gson:gson:2.8.6'
implementation 'io.reactivex:rxjava:1.3.4' implementation 'io.reactivex:rxjava:1.3.4'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'

View File

@@ -60,13 +60,7 @@
android:name=".ui.ServerActivity" android:name=".ui.ServerActivity"
android:windowSoftInputMode="stateUnchanged" /> android:windowSoftInputMode="stateUnchanged" />
<activity <activity
android:name=".ui.Server2Activity" android:name=".ui.ServerCustomConfigActivity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.Server3Activity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.Server4Activity"
android:windowSoftInputMode="stateUnchanged" /> android:windowSoftInputMode="stateUnchanged" />
<activity android:name=".ui.SettingsActivity" /> <activity android:name=".ui.SettingsActivity" />
<activity android:name=".ui.PerAppProxyActivity" /> <activity android:name=".ui.PerAppProxyActivity" />

BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so Normal file → Executable file

Binary file not shown.

BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so Normal file → Executable file

Binary file not shown.

BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so Normal file → Executable file

Binary file not shown.

BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so Normal file → Executable file

Binary file not shown.

View File

@@ -2,8 +2,7 @@ package com.v2ray.ang
import android.support.multidex.MultiDexApplication import android.support.multidex.MultiDexApplication
import android.support.v7.preference.PreferenceManager import android.support.v7.preference.PreferenceManager
import com.v2ray.ang.util.AngConfigManager import com.tencent.mmkv.MMKV
import me.dozen.dpreference.DPreference
class AngApplication : MultiDexApplication() { class AngApplication : MultiDexApplication() {
companion object { companion object {
@@ -14,8 +13,6 @@ class AngApplication : MultiDexApplication() {
var firstRun = false var firstRun = false
private set private set
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@@ -27,6 +24,6 @@ class AngApplication : MultiDexApplication() {
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply() defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE) //Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
AngConfigManager.inject(this) MMKV.initialize(this)
} }
} }

View File

@@ -6,18 +6,46 @@ package com.v2ray.ang
*/ */
object AppConfig { object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang" const val ANG_PACKAGE = "com.v2ray.ang"
const val ANG_CONFIG = "ang_config"
const val PREF_CURR_CONFIG = "pref_v2ray_config"
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid"
const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name"
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
const val PREF_CURR_CONFIG_OUTBOUND_TAGS = "pref_v2ray_config_outbound_tags"
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val PREF_MODE = "pref_mode"
const val VMESS_PROTOCOL: String = "vmess://" // legacy
const val SS_PROTOCOL: String = "ss://" const val ANG_CONFIG = "ang_config"
const val SOCKS_PROTOCOL: String = "socks://" const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
// Preferences mapped to MMKV
const val PREF_MODE = "pref_mode"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_MODE = "pref_routing_mode"
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps"
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
// const val PREF_SOCKS_PORT = "pref_socks_port"
// const val PREF_HTTP_PORT = "pref_http_port"
// const val PREF_DONATE = "pref_donate"
// const val PREF_LICENSES = "pref_licenses"
// const val PREF_FEEDBACK = "pref_feedback"
// const val PREF_TG_GROUP = "pref_tg_group"
// const val PREF_AUTO_RESTART = "pref_auto_restart"
const val HTTP_PROTOCOL: String = "http://"
const val HTTPS_PROTOCOL: String = "https://"
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service" const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity" const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click" const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
@@ -28,9 +56,6 @@ object AppConfig {
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid" const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
const val TASKER_DEFAULT_GUID = "Default" const val TASKER_DEFAULT_GUID = "Default"
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
const val TAG_AGENT = "proxy" const val TAG_AGENT = "proxy"
const val TAG_DIRECT = "direct" const val TAG_DIRECT = "direct"
const val TAG_BLOCKED = "block" const val TAG_BLOCKED = "block"

View File

@@ -1,12 +1,14 @@
package com.v2ray.ang.dto package com.v2ray.ang.dto
enum class EConfigType(val value: Int) { enum class EConfigType(val value: Int, val protocolScheme: String) {
VMESS(1), VMESS(1, "vmess://"),
CUSTOM(2), CUSTOM(2, ""),
SHADOWSOCKS(3), SHADOWSOCKS(3, "ss://"),
SOCKS(4); SOCKS(4, "socks://"),
VLESS(5, "vless://"),
TROJAN(6, "trojan://");
companion object { companion object {
fun fromInt(value: Int) = values().firstOrNull { it.value == value } fun fromInt(value: Int) = values().firstOrNull { it.value == value }
} }
} }

View File

@@ -0,0 +1,10 @@
package com.v2ray.ang.dto
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
fun getTestDelayString(): String {
if (testDelayMillis == 0L) {
return ""
}
return testDelayMillis.toString() + "ms"
}
}

View File

@@ -0,0 +1,69 @@
package com.v2ray.ang.dto
import com.v2ray.ang.AppConfig.TAG_AGENT
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.util.Utils
data class ServerConfig(
val configVersion: Int = 3,
val configType: EConfigType,
var subscriptionId: String = "",
val addedTime: Long = System.currentTimeMillis(),
var remarks: String = "",
val outboundBean: V2rayConfig.OutboundBean? = null,
var fullConfig: V2rayConfig? = null
) {
companion object {
fun create(configType: EConfigType): ServerConfig {
when(configType) {
EConfigType.VMESS, EConfigType.VLESS ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.toLowerCase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
EConfigType.CUSTOM ->
return ServerConfig(configType = configType)
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.toLowerCase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
}
}
}
fun getProxyOutbound(): V2rayConfig.OutboundBean? {
if (configType != EConfigType.CUSTOM) {
return outboundBean
}
return fullConfig?.getProxyOutbound()
}
fun getAllOutboundTags(): MutableList<String> {
if (configType != EConfigType.CUSTOM) {
return mutableListOf(TAG_AGENT, TAG_DIRECT, TAG_BLOCKED)
}
fullConfig?.let { config ->
return config.outbounds.map { it.tag }.toMutableList()
}
return mutableListOf()
}
fun getV2rayPointDomainAndPort(): String {
val address = getProxyOutbound()?.getServerAddress().orEmpty()
val port = getProxyOutbound()?.getServerPort()
return if (Utils.isIpv6Address(address)) {
String.format("[%s]:%s", address, port)
} else {
String.format("%s:%s", address, port)
}
}
}

View File

@@ -0,0 +1,8 @@
package com.v2ray.ang.dto
data class SubscriptionItem(
var remarks: String = "",
var url: String = "",
var enabled: Boolean = true,
val addedTime: Long = System.currentTimeMillis()) {
}

View File

@@ -1,159 +1,447 @@
package com.v2ray.ang.dto package com.v2ray.ang.dto
import android.text.TextUtils
import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
data class V2rayConfig( data class V2rayConfig(
val stats: Any?=null, var stats: Any? = null,
val log: LogBean, val log: LogBean,
val policy: PolicyBean, var policy: PolicyBean?,
val inbounds: ArrayList<InboundBean>, val inbounds: ArrayList<InboundBean>,
var outbounds: ArrayList<OutboundBean>, var outbounds: ArrayList<OutboundBean>,
var dns: DnsBean, var dns: DnsBean,
val routing: RoutingBean) { val routing: RoutingBean,
val api: Any? = null,
val transport: Any? = null,
val reverse: Any? = null,
var fakedns: FakednsBean? = null,
val browserForwarder: Any? = null) {
companion object {
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
const val DEFAULT_NETWORK = "tcp"
const val DEFAULT_FLOW = "xtls-rprx-splice"
const val TLS = "tls"
const val XTLS = "xtls"
const val HTTP = "http"
}
data class LogBean(val access: String, data class LogBean(val access: String,
val error: String, val error: String,
val loglevel: String) var loglevel: String?,
val dnsLog: Boolean? = null)
data class InboundBean( data class InboundBean(
var tag: String, var tag: String,
var port: Int, var port: Int,
var protocol: String, var protocol: String,
var listen: String?=null, var listen: String? = null,
val settings: InSettingsBean, val settings: Any? = null,
val sniffing: SniffingBean?) { val sniffing: SniffingBean?,
val streamSettings: Any? = null,
val allocate: Any? = null) {
data class InSettingsBean(val auth: String? = null, data class InSettingsBean(val auth: String? = null,
val udp: Boolean? = null, val udp: Boolean? = null,
val userLevel: Int? =null, val userLevel: Int? = null,
val address: String? = null, val address: String? = null,
val port: Int? = null, val port: Int? = null,
val network: String? = null) val network: String? = null)
data class SniffingBean(var enabled: Boolean, data class SniffingBean(var enabled: Boolean,
val destOverride: List<String>) val destOverride: ArrayList<String>,
val metadataOnly: Boolean? = null)
} }
data class OutboundBean(val tag: String, data class OutboundBean(val tag: String = "proxy",
var protocol: String, var protocol: String,
var settings: OutSettingsBean?, var settings: OutSettingsBean? = null,
var streamSettings: StreamSettingsBean?, var streamSettings: StreamSettingsBean? = null,
var mux: MuxBean?) { val proxySettings: Any? = null,
val sendThrough: String? = null,
val mux: MuxBean? = MuxBean(false)) {
data class OutSettingsBean(var vnext: List<VnextBean>?, data class OutSettingsBean(var vnext: List<VnextBean>? = null,
var servers: List<ServersBean>?, var servers: List<ServersBean>? = null,
var response: Response) { /*Blackhole*/
var response: Response? = null,
/*DNS*/
val network: String? = null,
val address: String? = null,
val port: Int? = null,
/*Freedom*/
var domainStrategy: String? = null,
val redirect: String? = null,
val userLevel: Int? = null,
/*Loopback*/
val inboundTag: String? = null) {
data class VnextBean(var address: String, data class VnextBean(var address: String = "",
var port: Int, var port: Int = DEFAULT_PORT,
var users: List<UsersBean>) { var users: List<UsersBean>) {
data class UsersBean(var id: String, data class UsersBean(var id: String = "",
var alterId: Int, var alterId: Int? = null,
var security: String, var security: String = DEFAULT_SECURITY,
var level: Int) var level: Int = DEFAULT_LEVEL,
var encryption: String = "",
var flow: String = "")
} }
data class ServersBean(var address: String, data class ServersBean(var address: String = "",
var method: String, var method: String = "chacha20-poly1305",
var ota: Boolean, var ota: Boolean = false,
var password: String, var password: String = "",
var port: Int, var port: Int = DEFAULT_PORT,
var level: Int) var level: Int = DEFAULT_LEVEL,
val email: String? = null,
val flow: String? = null,
val ivCheck: Boolean? = null,
var users: List<SocksUsersBean>? = null) {
data class SocksUsersBean(var user: String = "",
var pass: String = "",
var level: Int = DEFAULT_LEVEL)
}
data class Response(var type: String) data class Response(var type: String)
} }
data class StreamSettingsBean(var network: String, data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
var security: String, var security: String = "",
var tcpSettings: TcpSettingsBean?, var tcpSettings: TcpSettingsBean? = null,
var kcpSettings: KcpSettingsBean?, var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean?, var wsSettings: WsSettingsBean? = null,
var httpSettings: HttpSettingsBean?, var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean?, var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? var quicSettings: QuicSettingBean? = null,
var xtlsSettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null,
val dsSettings: Any? = null,
val sockopt: Any? = null
) { ) {
data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) { data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
val acceptProxyProtocol: Boolean? = null) {
data class HeaderBean(var type: String = "none", data class HeaderBean(var type: String = "none",
var request: Any? = null, var request: RequestBean? = null,
var response: Any? = null) var response: Any? = null) {
data class RequestBean(var path: List<String> = ArrayList(),
var headers: HeadersBean = HeadersBean(),
val version: String? = null,
val method: String? = null) {
data class HeadersBean(var Host: List<String> = ArrayList(),
@SerializedName("User-Agent")
val userAgent: List<String>? = null,
@SerializedName("Accept-Encoding")
val acceptEncoding: List<String>? = null,
val Connection: List<String>? = null,
val Pragma: String? = null)
}
}
} }
data class KcpSettingsBean(var mtu: Int = 1350, data class KcpSettingsBean(var mtu: Int = 1350,
var tti: Int = 20, var tti: Int = 50,
var uplinkCapacity: Int = 12, var uplinkCapacity: Int = 12,
var downlinkCapacity: Int = 100, var downlinkCapacity: Int = 100,
var congestion: Boolean = false, var congestion: Boolean = false,
var readBufferSize: Int = 1, var readBufferSize: Int = 1,
var writeBufferSize: Int = 1, var writeBufferSize: Int = 1,
var header: HeaderBean = HeaderBean()) { var header: HeaderBean = HeaderBean(),
var seed: String? = null) {
data class HeaderBean(var type: String = "none") data class HeaderBean(var type: String = "none")
} }
data class WsSettingsBean(var path: String = "", data class WsSettingsBean(var path: String = "",
var headers: HeadersBean = HeadersBean()) { var headers: HeadersBean = HeadersBean(),
val maxEarlyData: Int? = null,
val useBrowserForwarding: Boolean? = null,
val acceptProxyProtocol: Boolean? = null) {
data class HeadersBean(var Host: String = "") data class HeadersBean(var Host: String = "")
} }
data class HttpSettingsBean(var host: List<String> = ArrayList(), data class HttpSettingsBean(var host: List<String> = ArrayList(),
var path: String = "") var path: String = "")
data class TlsSettingsBean(var allowInsecure: Boolean = true, data class TlsSettingsBean(var allowInsecure: Boolean = false,
var serverName: String = "") var serverName: String = "",
val alpn: List<String>? = null,
val minVersion: String? = null,
val maxVersion: String? = null,
val preferServerCipherSuites: Boolean? = null,
val cipherSuites: String? = null,
val fingerprint: String? = null,
val certificates: List<Any>? = null,
val disableSystemRoot: Boolean? = null,
val enableSessionResumption: Boolean? = null)
data class QuicSettingBean(var security: String = "none", data class QuicSettingBean(var security: String = "none",
var key: String = "", var key: String = "",
var header: HeaderBean = HeaderBean()) { var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none") data class HeaderBean(var type: String = "none")
} }
data class GrpcSettingsBean(var serviceName: String = "",
var multiMode: Boolean? = null)
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?): String {
var sni = ""
network = transport
when (network) {
"tcp" -> {
val tcpSetting = TcpSettingsBean()
if (headerType == HTTP) {
tcpSetting.header.type = HTTP
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }
requestObj.path = (path ?: "").split(",").map { it.trim() }
tcpSetting.header.request = requestObj
sni = requestObj.headers.Host.getOrNull(0) ?: sni
}
} else {
tcpSetting.header.type = "none"
sni = host ?: ""
}
tcpSettings = tcpSetting
}
"kcp" -> {
val kcpsetting = KcpSettingsBean()
kcpsetting.header.type = headerType ?: "none"
if (seed.isNullOrEmpty()) {
kcpsetting.seed = null
} else {
kcpsetting.seed = seed
}
kcpSettings = kcpsetting
}
"ws" -> {
val wssetting = WsSettingsBean()
wssetting.headers.Host = host ?: ""
sni = wssetting.headers.Host
wssetting.path = path ?: "/"
wsSettings = wssetting
}
"h2", "http" -> {
network = "h2"
val h2Setting = HttpSettingsBean()
h2Setting.host = (host ?: "").split(",").map { it.trim() }
sni = h2Setting.host.getOrNull(0) ?: sni
h2Setting.path = path ?: "/"
httpSettings = h2Setting
}
"quic" -> {
val quicsetting = QuicSettingBean()
quicsetting.security = quicSecurity ?: "none"
quicsetting.key = key ?: ""
quicsetting.header.type = headerType ?: "none"
quicSettings = quicsetting
}
"grpc" -> {
val grpcSetting = GrpcSettingsBean()
grpcSetting.multiMode = mode == "multi"
grpcSetting.serviceName = serviceName ?: ""
sni = host ?: ""
grpcSettings = grpcSetting
}
}
return sni
}
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String) {
security = streamSecurity
val tlsSetting = TlsSettingsBean(
allowInsecure = allowInsecure,
serverName = sni
)
if (security == TLS) {
tlsSettings = tlsSetting
} else if (security == XTLS) {
xtlsSettings = tlsSetting
}
}
} }
data class MuxBean(var enabled: Boolean) data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
fun getServerAddress(): String? { fun getServerAddress(): String? {
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) { if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.address return settings?.vnext?.get(0)?.address
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) { } else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) {
return settings?.servers?.get(0)?.address return settings?.servers?.get(0)?.address
} }
return null return null
} }
fun getServerPort(): Int? { fun getServerPort(): Int? {
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) { if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.port return settings?.vnext?.get(0)?.port
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) { } else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) {
return settings?.servers?.get(0)?.port return settings?.servers?.get(0)?.port
} }
return null return null
} }
fun getPassword(): String? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.users?.get(0)?.id
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) {
return settings?.servers?.get(0)?.password
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
return settings?.servers?.get(0)?.users?.get(0)?.pass
}
return null
}
fun getSecurityEncryption(): String? {
return when {
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.security
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.encryption
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.get(0)?.method
else -> null
}
}
fun getTransportSettingDetails(): List<String>? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
val transport = streamSettings?.network ?: return null
return when (transport) {
"tcp" -> {
val tcpSetting = streamSettings?.tcpSettings ?: return null
listOf(tcpSetting.header.type,
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
tcpSetting.header.request?.path?.joinToString().orEmpty())
}
"kcp" -> {
val kcpSetting = streamSettings?.kcpSettings ?: return null
listOf(kcpSetting.header.type,
"",
kcpSetting.seed.orEmpty())
}
"ws" -> {
val wsSetting = streamSettings?.wsSettings ?: return null
listOf("",
wsSetting.headers.Host,
wsSetting.path)
}
"h2" -> {
val h2Setting = streamSettings?.httpSettings ?: return null
listOf("",
h2Setting.host.joinToString(),
h2Setting.path)
}
"quic" -> {
val quicSetting = streamSettings?.quicSettings ?: return null
listOf(quicSetting.header.type,
quicSetting.security,
quicSetting.key)
}
"grpc" -> {
val grpcSetting = streamSettings?.grpcSettings ?: return null
listOf(if (grpcSetting.multiMode == true) "multi" else "gun",
"",
grpcSetting.serviceName)
}
else -> null
}
}
return null
}
} }
//data class DnsBean(var servers: List<String>) data class DnsBean(var servers: ArrayList<Any>? = null,
data class DnsBean(var servers: List<Any>?=null, var hosts: Map<String, String>? = null,
var hosts: Map<String, String>?=null val clientIp: String? = null,
val disableCache: Boolean? = null,
val queryStrategy: String? = null,
val tag: String? = null
) { ) {
data class ServersBean(var address: String = "", data class ServersBean(var address: String = "",
var port: Int = 0, var port: Int? = null,
var domains: List<String>?) var domains: List<String>? = null,
var expectIPs: List<String>? = null,
val clientIp: String? = null)
} }
data class RoutingBean(var domainStrategy: String, data class RoutingBean(var domainStrategy: String,
var rules: ArrayList<RulesBean>) { val domainMatcher: String? = null,
var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null) {
data class RulesBean(var type: String = "", data class RulesBean(var type: String = "",
var ip: ArrayList<String>? = null, var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null, var domain: ArrayList<String>? = null,
var outboundTag: String = "", var outboundTag: String = "",
var balancerTag: String? = null,
var port: String? = null, var port: String? = null,
var inboundTag: ArrayList<String>? = null) val sourcePort: String? = null,
val network: String? = null,
val source: List<String>? = null,
val user: List<String>? = null,
var inboundTag: List<String>? = null,
val protocol: List<String>? = null,
val attrs: String? = null,
val domainMatcher: String? = null
)
} }
data class PolicyBean(var levels: Map<String, LevelBean>, data class PolicyBean(var levels: Map<String, LevelBean>,
var system: Any?=null) { var system: Any? = null) {
data class LevelBean( data class LevelBean(
var handshake: Int? = null, var handshake: Int? = null,
var connIdle: Int? = null, var connIdle: Int? = null,
var uplinkOnly: Int? = null, var uplinkOnly: Int? = null,
var downlinkOnly: Int? = null) var downlinkOnly: Int? = null,
val statsUserUplink: Boolean? = null,
val statsUserDownlink: Boolean? = null,
var bufferSize: Int? = null)
} }
}
data class FakednsBean(var ipPool: String = "198.18.0.0/15",
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
fun getProxyOutbound(): OutboundBean? {
outbounds.forEach { outbound ->
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
outbound.protocol.equals(EConfigType.VLESS.name, true) ||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
outbound.protocol.equals(EConfigType.SOCKS.name, true) ||
outbound.protocol.equals(EConfigType.TROJAN.name, true)) {
return outbound
}
}
return null
}
fun toPrettyPrinting(): String {
return GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
object : TypeToken<Double>() {}.type,
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? -> JsonPrimitive(src?.toInt()) }
)
.create()
.toJson(this)
}
}

View File

@@ -10,4 +10,5 @@ data class VmessQRCode(var v: String = "",
var type: String = "", var type: String = "",
var host: String = "", var host: String = "",
var path: String = "", var path: String = "",
var tls: String = "") var tls: String = "",
var sni: String = "")

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.os.Build import android.os.Build
import android.widget.Toast import android.widget.Toast
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import me.dozen.dpreference.DPreference
import me.drakeet.support.toast.ToastCompat import me.drakeet.support.toast.ToastCompat
import org.json.JSONObject import org.json.JSONObject
import java.net.URLConnection import java.net.URLConnection
@@ -16,13 +15,6 @@ import java.net.URLConnection
val Context.v2RayApplication: AngApplication val Context.v2RayApplication: AngApplication
get() = applicationContext as AngApplication get() = applicationContext as AngApplication
// Usage note: DPreference use Android ContentProvider to redirect multi process access to main process.
// Currently, RunSoLibV2RayDaemon process will run proxy core, keep minimum configuration and long running
// in the background, support toggle on/off. That means it should NOT use DPreference after the initial
// creation and setup of the service
val Context.defaultDPreference: DPreference
get() = v2RayApplication.defaultDPreference
inline fun Context.toast(message: Int): Toast = ToastCompat inline fun Context.toast(message: Int): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT) .makeText(this, message, Toast.LENGTH_SHORT)
.apply { .apply {
@@ -35,7 +27,7 @@ inline fun Context.toast(message: CharSequence): Toast = ToastCompat
show() show()
} }
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)!! fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) } fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
const val threshold = 1000 const val threshold = 1000

View File

@@ -1,10 +0,0 @@
package com.v2ray.ang.extension
import android.preference.Preference
fun Preference.onClick(listener: () -> Unit) {
setOnPreferenceClickListener {
listener()
true
}
}

View File

@@ -5,11 +5,16 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import com.google.zxing.WriterException import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
class TaskerReceiver : BroadcastReceiver() { class TaskerReceiver : BroadcastReceiver() {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
override fun onReceive(context: Context, intent: Intent?) { override fun onReceive(context: Context, intent: Intent?) {
try { try {
@@ -23,7 +28,8 @@ class TaskerReceiver : BroadcastReceiver() {
if (guid == AppConfig.TASKER_DEFAULT_GUID) { if (guid == AppConfig.TASKER_DEFAULT_GUID) {
Utils.startVServiceFromToggle(context) Utils.startVServiceFromToggle(context)
} else { } else {
Utils.startVService(context, guid) mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
V2RayServiceManager.startV2Ray(context)
} }
} else { } else {
Utils.stopVService(context) Utils.stopVService(context)

View File

@@ -25,7 +25,7 @@ class QSTileService : TileService() {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle) qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
} else if (state == Tile.STATE_ACTIVE) { } else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = V2RayServiceManager.currentConfigName qsTile?.label = V2RayServiceManager.currentConfig?.remarks
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v) qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
} }

View File

@@ -13,17 +13,18 @@ import android.os.Build
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import android.util.Log import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.TAG_DIRECT import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.toSpeedString import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq import go.Seq
import libv2ray.Libv2ray import libv2ray.Libv2ray
import libv2ray.V2RayPoint import libv2ray.V2RayPoint
@@ -41,6 +42,8 @@ object V2RayServiceManager {
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback()) val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private val mMsgReceive = ReceiveMessageHandler() private val mMsgReceive = ReceiveMessageHandler()
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
var serviceControl: SoftReference<ServiceControl>? = null var serviceControl: SoftReference<ServiceControl>? = null
set(value) { set(value) {
@@ -52,7 +55,7 @@ object V2RayServiceManager {
Seq.setContext(context) Seq.setContext(context)
} }
} }
var currentConfigName = "NG" var currentConfig: ServerConfig? = null
private var lastQueryTime = 0L private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null private var mBuilder: NotificationCompat.Builder? = null
@@ -60,12 +63,12 @@ object V2RayServiceManager {
private var mNotificationManager: NotificationManager? = null private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context) { fun startV2Ray(context: Context) {
if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) { if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
context.toast(R.string.toast_warning_pref_proxysharing_short) context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{ }else{
context.toast(R.string.toast_services_start) context.toast(R.string.toast_services_start)
} }
val intent = if (context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") { val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
Intent(context.applicationContext, V2RayVpnService::class.java) Intent(context.applicationContext, V2RayVpnService::class.java)
} else { } else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java) Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -123,7 +126,12 @@ object V2RayServiceManager {
fun startV2rayPoint() { fun startV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return val service = serviceControl?.get()?.getService() ?: return
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (!v2rayPoint.isRunning) { if (!v2rayPoint.isRunning) {
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
if (!result.status)
return
try { try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE) val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
@@ -135,12 +143,12 @@ object V2RayServiceManager {
Log.d(service.packageName, e.toString()) Log.d(service.packageName, e.toString())
} }
v2rayPoint.configureFileContent = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") v2rayPoint.configureFileContent = result.content
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false) v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false) currentConfig = config
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "") v2rayPoint.enableLocalDNS = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN" v2rayPoint.forwardIpv6 = settingsStorage?.decodeBool(AppConfig.PREF_FORWARD_IPV6) ?: false
currentConfigName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG") v2rayPoint.proxyOnly = settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" != "VPN"
try { try {
v2rayPoint.runLoop() v2rayPoint.runLoop()
@@ -244,7 +252,7 @@ object V2RayServiceManager {
mBuilder = NotificationCompat.Builder(service, channelId) mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_v) .setSmallIcon(R.drawable.ic_v)
.setContentTitle(currentConfigName) .setContentTitle(currentConfig?.remarks)
.setPriority(NotificationCompat.PRIORITY_MIN) .setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true) .setOngoing(true)
.setShowWhen(false) .setShowWhen(false)
@@ -281,7 +289,7 @@ object V2RayServiceManager {
mSubscription = null mSubscription = null
} }
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) { private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
if (mBuilder != null) { if (mBuilder != null) {
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) { if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
mBuilder?.setSmallIcon(R.drawable.ic_v) mBuilder?.setSmallIcon(R.drawable.ic_v)
@@ -305,13 +313,12 @@ object V2RayServiceManager {
} }
fun startSpeedNotification() { fun startSpeedNotification() {
val service = serviceControl?.get()?.getService() ?: return
if (mSubscription == null && if (mSubscription == null &&
v2rayPoint.isRunning && v2rayPoint.isRunning &&
service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) { settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
var lastZeroSpeed = false var lastZeroSpeed = false
val outboundTags = service.defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet()) val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags.remove(TAG_DIRECT) outboundTags?.remove(TAG_DIRECT)
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS) mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe { .subscribe {
@@ -319,7 +326,7 @@ object V2RayServiceManager {
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0 val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
var proxyTotal = 0L var proxyTotal = 0L
val text = StringBuilder() val text = StringBuilder()
outboundTags.forEach { outboundTags?.forEach {
val up = v2rayPoint.queryStats(it, "uplink") val up = v2rayPoint.queryStats(it, "uplink")
val down = v2rayPoint.queryStats(it, "downlink") val down = v2rayPoint.queryStats(it, "downlink")
if (up + down > 0) { if (up + down > 0) {
@@ -332,7 +339,7 @@ object V2RayServiceManager {
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L) val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
if (!zeroSpeed || !lastZeroSpeed) { if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) { if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0) appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
} }
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds, appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds) directDownlink / sinceLastQueryInSeconds)
@@ -358,7 +365,7 @@ object V2RayServiceManager {
if (mSubscription != null) { if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats mSubscription?.unsubscribe() //stop queryStats
mSubscription = null mSubscription = null
updateNotification(currentConfigName, 0, 0) updateNotification(currentConfig?.remarks, 0, 0)
} }
} }
} }

View File

@@ -10,10 +10,10 @@ import android.os.ParcelFileDescriptor
import android.os.StrictMode import android.os.StrictMode
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.util.Log import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@@ -22,6 +22,8 @@ import java.io.File
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
class V2RayVpnService : VpnService(), ServiceControl { class V2RayVpnService : VpnService(), ServiceControl {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private lateinit var mInterface: ParcelFileDescriptor private lateinit var mInterface: ParcelFileDescriptor
/** /**
@@ -91,8 +93,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
// If the old interface has exactly the same parameters, use it! // If the old interface has exactly the same parameters, use it!
// Configure a builder while parsing the parameters. // Configure a builder while parsing the parameters.
val builder = Builder() val builder = Builder()
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false) val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
val routingMode = defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0") val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
parameters.split(" ") parameters.split(" ")
.map { it.split(",") } .map { it.split(",") }
@@ -120,18 +122,20 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
if(!enableLocalDns) { if(!enableLocalDns) {
Utils.getRemoteDnsServers(defaultDPreference) Utils.getVpnDnsServers()
.forEach { .forEach {
builder.addDnsServer(it) if (Utils.isPureIpAddress(it)) {
} builder.addDnsServer(it)
}
}
} }
builder.setSession(V2RayServiceManager.currentConfigName) builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) { settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
val apps = defaultDPreference.getPrefStringSet(PerAppProxyActivity.PREF_PER_APP_PROXY_SET, null) val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = defaultDPreference.getPrefBoolean(PerAppProxyActivity.PREF_BYPASS_APPS, false) val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
apps?.forEach { apps?.forEach {
try { try {
if (bypassApps) if (bypassApps)

View File

@@ -1,199 +0,0 @@
package com.v2ray.ang.ui
import android.app.ActivityOptions
import android.app.FragmentManager
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.view.View
import com.v2ray.ang.R
abstract class BaseDrawerActivity : BaseActivity() {
companion object {
private val TAG = "BaseDrawerActivity"
}
private var mToolbar: Toolbar? = null
private var mDrawerToggle: ActionBarDrawerToggle? = null
private var mDrawerLayout: DrawerLayout? = null
private var mToolbarInitialized: Boolean = false
private var mItemToOpenWhenDrawerCloses = -1
private val backStackChangedListener = FragmentManager.OnBackStackChangedListener { updateDrawerToggle() }
private val drawerListener = object : DrawerLayout.DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
mDrawerToggle!!.onDrawerSlide(drawerView, slideOffset)
}
override fun onDrawerOpened(drawerView: View) {
mDrawerToggle!!.onDrawerOpened(drawerView)
//supportActionBar!!.setTitle(R.string.app_name)
}
override fun onDrawerClosed(drawerView: View) {
mDrawerToggle!!.onDrawerClosed(drawerView)
if (mItemToOpenWhenDrawerCloses >= 0) {
val extras = ActivityOptions.makeCustomAnimation(
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
var activityClass: Class<*>? = null
when (mItemToOpenWhenDrawerCloses) {
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> {
startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
return
}
R.id.donate -> {
// startActivity<InappBuyActivity>()
return
}
}
if (activityClass != null) {
startActivity(Intent(this@BaseDrawerActivity, activityClass), extras)
finish()
}
}
}
override fun onDrawerStateChanged(newState: Int) {
mDrawerToggle!!.onDrawerStateChanged(newState)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Activity onCreate")
}
override fun onStart() {
super.onStart()
if (!mToolbarInitialized) {
throw IllegalStateException("You must run super.initializeToolbar at " + "the end of your onCreate method")
}
}
public override fun onResume() {
super.onResume()
// Whenever the fragment back stack changes, we may need to update the
// action bar toggle: only top level screens show the hamburger-like icon, inner
// screens - either Activities or fragments - show the "Up" icon instead.
fragmentManager.addOnBackStackChangedListener(backStackChangedListener)
}
public override fun onPause() {
super.onPause()
fragmentManager.removeOnBackStackChangedListener(backStackChangedListener)
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
mDrawerToggle!!.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mDrawerToggle!!.onConfigurationChanged(newConfig)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (mDrawerToggle != null && mDrawerToggle!!.onOptionsItemSelected(item)) {
return true
}
// If not handled by drawerToggle, home needs to be handled by returning to previous
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
// If the drawer is open, back will close it
if (mDrawerLayout != null && mDrawerLayout!!.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout!!.closeDrawers()
return
}
// Otherwise, it may return to the previous fragment stack
val fragmentManager = fragmentManager
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack()
} else {
// Lastly, it will rely on the system behavior for back
super.onBackPressed()
}
}
private fun updateDrawerToggle() {
if (mDrawerToggle == null) {
return
}
val isRoot = fragmentManager.backStackEntryCount == 0
mDrawerToggle!!.isDrawerIndicatorEnabled = isRoot
supportActionBar!!.setDisplayShowHomeEnabled(!isRoot)
supportActionBar!!.setDisplayHomeAsUpEnabled(!isRoot)
supportActionBar!!.setHomeButtonEnabled(!isRoot)
if (isRoot) {
mDrawerToggle!!.syncState()
}
}
protected fun initializeToolbar() {
mToolbar = findViewById<View>(R.id.toolbar) as Toolbar
if (mToolbar == null) {
throw IllegalStateException("Layout is required to include a Toolbar with id " + "'toolbar'")
}
// mToolbar.inflateMenu(R.menu.main);
mDrawerLayout = findViewById<View>(R.id.drawer_layout) as DrawerLayout
if (mDrawerLayout != null) {
val navigationView = findViewById<View>(R.id.nav_view) as NavigationView
?: throw IllegalStateException("Layout requires a NavigationView " + "with id 'nav_view'")
// Create an ActionBarDrawerToggle that will handle opening/closing of the drawer:
mDrawerToggle = ActionBarDrawerToggle(this, mDrawerLayout,
mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
mDrawerLayout!!.addDrawerListener(drawerListener)
populateDrawerItems(navigationView)
setSupportActionBar(mToolbar)
updateDrawerToggle()
} else {
setSupportActionBar(mToolbar)
}
mToolbarInitialized = true
}
private fun populateDrawerItems(navigationView: NavigationView) {
navigationView.setNavigationItemSelectedListener { menuItem ->
menuItem.isChecked = true
mItemToOpenWhenDrawerCloses = menuItem.itemId
mDrawerLayout!!.closeDrawers()
true
}
if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.sub_setting)
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.settings)
}
}
}

View File

@@ -16,22 +16,26 @@ import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import com.v2ray.ang.viewmodel.MainViewModel import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import libv2ray.Libv2ray import libv2ray.Libv2ray
import me.drakeet.support.toast.ToastCompat
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.net.URL import java.net.URL
@@ -46,8 +50,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
private val adapter by lazy { MainRecyclerAdapter(this) } private val adapter by lazy { MainRecyclerAdapter(this) }
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private var mItemTouchHelper: ItemTouchHelper? = null private var mItemTouchHelper: ItemTouchHelper? = null
private val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) } val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -58,7 +64,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
fab.setOnClickListener { fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) { if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this) Utils.stopVService(this)
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") { } else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
if (intent == null) { if (intent == null) {
startV2Ray() startV2Ray()
@@ -95,47 +101,65 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})" version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver() setupViewModelObserver()
migrateLegacy()
} }
private fun setupViewModelObserver() { private fun setupViewModelObserver() {
mainViewModel.updateListAction.observe(this, { mainViewModel.updateListAction.observe(this, {
val index = it ?: return@observe val index = it ?: return@observe
if (index >= 0) { if (index >= 0) {
adapter.updateSelectedItem(index) adapter.notifyItemChanged(index)
} else { } else {
adapter.updateConfigList() adapter.notifyDataSetChanged()
} }
}) })
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it }) mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
mainViewModel.isRunning.observe(this, { mainViewModel.isRunning.observe(this, {
val isRunning = it ?: return@observe val isRunning = it ?: return@observe
adapter.changeable = !isRunning adapter.isRunning = isRunning
if (isRunning) { if (isRunning) {
fab.setImageResource(R.drawable.ic_v) fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected) tv_test_state.text = getString(R.string.connection_connected)
layout_test.isFocusable = true
} else { } else {
fab.setImageResource(R.drawable.ic_v_idle) fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected) tv_test_state.text = getString(R.string.connection_not_connected)
layout_test.isFocusable = false
} }
hideCircle() hideCircle()
}) })
mainViewModel.startListenBroadcast() mainViewModel.startListenBroadcast()
} }
private fun migrateLegacy() {
GlobalScope.launch(Dispatchers.IO) {
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
if (result != null) {
launch(Dispatchers.Main) {
if (result) {
toast(getString(R.string.migration_success))
mainViewModel.reloadServerList()
} else {
toast(getString(R.string.migration_fail))
}
}
}
}
}
fun startV2Ray() { fun startV2Ray() {
if (AngConfigManager.configs.index < 0) { if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
return return
} }
showCircle() showCircle()
// toast(R.string.toast_services_start) // toast(R.string.toast_services_start)
if (!Utils.startVService(this, AngConfigManager.configs.index)) { V2RayServiceManager.startV2Ray(this)
hideCircle() hideCircle()
}
} }
public override fun onResume() { public override fun onResume() {
super.onResume() super.onResume()
adapter.updateConfigList() mainViewModel.reloadServerList()
} }
public override fun onPause() { public override fun onPause() {
@@ -171,9 +195,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true return true
} }
private fun getOptionIntent() = Intent().putExtra("position", -1)
.putExtra("isRunning", mainViewModel.isRunning.value == true)
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.import_qrcode -> { R.id.import_qrcode -> {
importQRcode(REQUEST_SCAN) importQRcode(REQUEST_SCAN)
@@ -184,18 +205,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true true
} }
R.id.import_manually_vmess -> { R.id.import_manually_vmess -> {
startActivity(getOptionIntent().setClass(this, ServerActivity::class.java)) startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
adapter.updateConfigList() setClass(this, ServerActivity::class.java))
true true
} }
R.id.import_manually_ss -> { R.id.import_manually_ss -> {
startActivity(getOptionIntent().setClass(this, Server3Activity::class.java)) startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
adapter.updateConfigList() setClass(this, ServerActivity::class.java))
true true
} }
R.id.import_manually_socks -> { R.id.import_manually_socks -> {
startActivity(getOptionIntent().setClass(this, Server4Activity::class.java)) startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
adapter.updateConfigList() setClass(this, ServerActivity::class.java))
true true
} }
R.id.import_config_custom_clipboard -> { R.id.import_config_custom_clipboard -> {
@@ -226,8 +247,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
R.id.export_all -> { R.id.export_all -> {
if (AngConfigManager.shareAll2Clipboard() == 0) { if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
//remove toast, otherwise it will block previous warning message toast(R.string.toast_success)
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
} }
@@ -288,10 +309,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
fun importBatchConfig(server: String?, subid: String = "") { fun importBatchConfig(server: String?, subid: String = "") {
val count = AngConfigManager.importBatchConfig(server, subid) var count = AngConfigManager.importBatchConfig(server, subid)
if (count <= 0) {
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid)
}
if (count > 0) { if (count > 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
adapter.updateConfigList() mainViewModel.reloadServerList()
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
} }
@@ -375,18 +399,16 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
: Boolean { : Boolean {
try { try {
toast(R.string.title_sub_update) toast(R.string.title_sub_update)
val subItem = AngConfigManager.configs.subItem MmkvManager.decodeSubscriptions().forEach {
for (k in 0 until subItem.count()) { if (TextUtils.isEmpty(it.first)
if (TextUtils.isEmpty(subItem[k].id) || TextUtils.isEmpty(it.second.remarks)
|| TextUtils.isEmpty(subItem[k].remarks) || TextUtils.isEmpty(it.second.url)
|| TextUtils.isEmpty(subItem[k].url)
) { ) {
continue return@forEach
} }
val id = subItem[k].id val url = it.second.url
val url = subItem[k].url
if (!Utils.isValidUrl(url)) { if (!Utils.isValidUrl(url)) {
continue return@forEach
} }
Log.d("Main", url) Log.d("Main", url)
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
@@ -397,7 +419,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
"" ""
} }
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
importBatchConfig(Utils.decode(configText), id) importBatchConfig(Utils.decode(configText), it.first)
} }
} }
} }
@@ -434,9 +456,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.subscribe { .subscribe {
if (it) { if (it) {
try { try {
contentResolver.openInputStream(uri).use { contentResolver.openInputStream(uri).use { input ->
val configText = it?.bufferedReader()?.readText() importCustomizeConfig(input?.bufferedReader()?.readText())
importCustomizeConfig(configText)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -450,19 +471,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import customize config * import customize config
*/ */
fun importCustomizeConfig(server: String?) { fun importCustomizeConfig(server: String?) {
if (server == null) { try {
return if (server == null || TextUtils.isEmpty(server)) {
} toast(R.string.toast_none_data)
if (!V2rayConfigUtil.isValidConfig(server)) { return
toast(R.string.toast_config_file_invalid) }
return mainViewModel.appendCustomConfigServer(server)
}
val resId = AngConfigManager.importCustomizeConfig(server)
if (resId > 0) {
toast(resId)
} else {
toast(R.string.toast_success) toast(R.string.toast_success)
adapter.updateConfigList() adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
} catch (e: Exception) {
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
e.printStackTrace()
return
} }
} }

View File

@@ -2,22 +2,25 @@ package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.TextUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.android.synthetic.main.item_qrcode.view.* import kotlinx.android.synthetic.main.item_qrcode.view.*
import kotlinx.android.synthetic.main.item_recycler_main.view.* import kotlinx.android.synthetic.main.item_recycler_main.view.*
import rx.Observable import rx.Observable
@@ -32,83 +35,70 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
private var mActivity: MainActivity = activity private var mActivity: MainActivity = activity
private lateinit var configs: AngConfig private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val share_method: Array<out String> by lazy { private val share_method: Array<out String> by lazy {
mActivity.resources.getStringArray(R.array.share_method) mActivity.resources.getStringArray(R.array.share_method)
} }
var isRunning = false
var changeable: Boolean = true override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
set(value) {
if (field == value)
return
field = value
notifyDataSetChanged()
}
init {
updateConfigList()
}
override fun getItemCount() = configs.vmess.count() + 1
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) { if (holder is MainViewHolder) {
val configType = EConfigType.fromInt(configs.vmess[position].configType) val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
val remarks = configs.vmess[position].remarks val config = mActivity.mainViewModel.serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?: return
val subid = configs.vmess[position].subid val outbound = config.getProxyOutbound()
val address = configs.vmess[position].address val aff = MmkvManager.decodeServerAffiliationInfo(guid)
val port = configs.vmess[position].port
val test_result = configs.vmess[position].testResult
holder.name.text = remarks holder.name.text = config.remarks
holder.radio.isChecked = (position == configs.index) holder.radio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = test_result holder.test_result.text = aff?.getTestDelayString() ?: ""
if (aff?.testDelayMillis?:0L < 0L) {
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
} else {
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
}
holder.subscription.text = "" holder.subscription.text = ""
if (!TextUtils.isEmpty(subid)) { val json = subStorage?.decodeString(config.subscriptionId)
for (sub in configs.subItem) { if (!json.isNullOrBlank()) {
if (sub.id == subid) { val sub = Gson().fromJson(json, SubscriptionItem::class.java)
holder.subscription.text = sub.remarks holder.subscription.text = sub.remarks
}
}
} }
var shareOptions = share_method.asList() var shareOptions = share_method.asList()
if (configType == EConfigType.CUSTOM) { if (config.configType == EConfigType.CUSTOM) {
holder.type.text = mActivity.getString(R.string.server_customize_config) holder.type.text = mActivity.getString(R.string.server_customize_config)
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
if (serverOutbound == null) {
holder.statistics.text = ""
} else {
holder.statistics.text = "${serverOutbound.getServerAddress()} : ${serverOutbound.getServerPort()}"
}
shareOptions = shareOptions.takeLast(1) shareOptions = shareOptions.takeLast(1)
} else if (config.configType == EConfigType.VLESS) {
holder.type.text = config.configType.name
} else { } else {
holder.type.text = configType?.name?.toLowerCase() holder.type.text = config.configType.name.toLowerCase()
holder.statistics.text = "$address : $port"
} }
holder.statistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
holder.layout_share.setOnClickListener { holder.layout_share.setOnClickListener {
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i -> AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
try { try {
when (i) { when (i) {
0 -> { 0 -> {
if (configType == EConfigType.CUSTOM) { if (config.configType == EConfigType.CUSTOM) {
shareFullContent(position) shareFullContent(guid)
} else { } else {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null) val iv = LayoutInflater.from(mActivity).inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position)) iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
AlertDialog.Builder(mActivity).setView(iv).show() AlertDialog.Builder(mActivity).setView(iv).show()
} }
} }
1 -> { 1 -> {
if (AngConfigManager.share2Clipboard(position) == 0) { if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success) mActivity.toast(R.string.toast_success)
} else { } else {
mActivity.toast(R.string.toast_failure) mActivity.toast(R.string.toast_failure)
} }
} }
2 -> shareFullContent(position) 2 -> shareFullContent(guid)
else -> mActivity.toast("else") else -> mActivity.toast("else")
} }
} catch (e: Exception) { } catch (e: Exception) {
@@ -118,43 +108,39 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
holder.layout_edit.setOnClickListener { holder.layout_edit.setOnClickListener {
val intent = Intent().putExtra("position", position) val intent = Intent().putExtra("guid", guid)
.putExtra("isRunning", !changeable) .putExtra("isRunning", isRunning)
if (configType == EConfigType.VMESS) { if (config.configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
} else {
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java)) mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
} else if (configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, Server2Activity::class.java))
} else if (configType == EConfigType.SHADOWSOCKS) {
mActivity.startActivity(intent.setClass(mActivity, Server3Activity::class.java))
} else if (configType == EConfigType.SOCKS) {
mActivity.startActivity(intent.setClass(mActivity, Server4Activity::class.java))
} }
} }
holder.layout_remove.setOnClickListener { holder.layout_remove.setOnClickListener {
if (configs.index != position) { if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
if (AngConfigManager.removeServer(position) == 0) { mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position) notifyItemRemoved(position)
updateSelectedItem(position) notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
}
} }
} }
holder.infoContainer.setOnClickListener { holder.infoContainer.setOnClickListener {
if (changeable) { val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
AngConfigManager.setActiveServer(position) if (guid != selected) {
} else { mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
mActivity.showCircle() notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
Utils.stopVService(mActivity) notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
Observable.timer(500, TimeUnit.MILLISECONDS) if (isRunning) {
.observeOn(AndroidSchedulers.mainThread()) mActivity.showCircle()
.subscribe { Utils.stopVService(mActivity)
mActivity.showCircle() Observable.timer(500, TimeUnit.MILLISECONDS)
if (!Utils.startVService(mActivity, position)) { .observeOn(AndroidSchedulers.mainThread())
.subscribe {
V2RayServiceManager.startV2Ray(mActivity)
mActivity.hideCircle() mActivity.hideCircle()
} }
} }
} }
notifyDataSetChanged()
} }
} }
if (holder is FooterViewHolder) { if (holder is FooterViewHolder) {
@@ -163,14 +149,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.layout_edit.visibility = View.INVISIBLE holder.layout_edit.visibility = View.INVISIBLE
} else { } else {
holder.layout_edit.setOnClickListener { holder.layout_edit.setOnClickListener {
Utils.openUri(mActivity, AppConfig.promotionUrl) Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
} }
} }
} }
} }
private fun shareFullContent(position: Int) { private fun shareFullContent(guid: String) {
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) { if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success) mActivity.toast(R.string.toast_success)
} else { } else {
mActivity.toast(R.string.toast_failure) mActivity.toast(R.string.toast_failure)
@@ -178,42 +164,28 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) { return when (viewType) {
VIEW_TYPE_ITEM -> VIEW_TYPE_ITEM ->
return MainViewHolder(LayoutInflater.from(parent.context) MainViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_main, parent, false)) .inflate(R.layout.item_recycler_main, parent, false))
else -> else ->
return FooterViewHolder(LayoutInflater.from(parent.context) FooterViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_footer, parent, false)) .inflate(R.layout.item_recycler_footer, parent, false))
} }
} }
fun updateConfigList() {
configs = AngConfigManager.configs
notifyDataSetChanged()
}
// fun updateSelectedItem() {
// updateSelectedItem(configs.index)
// }
fun updateSelectedItem(pos: Int) {
//notifyItemChanged(pos)
notifyItemRangeChanged(pos, itemCount - pos)
}
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
if (position == configs.vmess.count()) { return if (position == mActivity.mainViewModel.serverList.size) {
return VIEW_TYPE_FOOTER VIEW_TYPE_FOOTER
} else { } else {
return VIEW_TYPE_ITEM VIEW_TYPE_ITEM
} }
} }
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder { class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
val subscription = itemView.tv_subscription val subscription = itemView.tv_subscription!!
val radio = itemView.btn_radio!! val radio = itemView.btn_radio!!
val name = itemView.tv_name!! val name = itemView.tv_name!!
val test_result = itemView.tv_test_result!! val test_result = itemView.tv_test_result!!
@@ -246,28 +218,26 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
override fun onItemDismiss(position: Int) { override fun onItemDismiss(position: Int) {
if (configs.index != position) { val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
// mActivity.alert(R.string.del_config_comfirm) { // mActivity.alert(R.string.del_config_comfirm) {
// positiveButton(android.R.string.ok) { // positiveButton(android.R.string.ok) {
if (AngConfigManager.removeServer(position) == 0) { mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position) notifyItemRemoved(position)
}
// } // }
// show() // show()
// } // }
} }
updateSelectedItem(position)
} }
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean { override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
AngConfigManager.swapServer(fromPosition, toPosition) mActivity.mainViewModel.swapServer(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition) notifyItemMoved(fromPosition, toPosition)
//notifyDataSetChanged() //notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
return true return true
} }
override fun onItemMoveCompleted() { override fun onItemMoveCompleted() {
AngConfigManager.storeConfigFile() // do nothing
} }
} }

View File

@@ -4,6 +4,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v7.preference.PreferenceManager
import android.support.v7.widget.DividerItemDecoration import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
@@ -15,7 +16,6 @@ import android.view.View
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.AppManagerUtil import com.v2ray.ang.util.AppManagerUtil
import kotlinx.android.synthetic.main.activity_bypass_list.* import kotlinx.android.synthetic.main.activity_bypass_list.*
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
@@ -35,13 +35,10 @@ import kotlinx.coroutines.launch
import java.net.URL import java.net.URL
class PerAppProxyActivity : BaseActivity() { class PerAppProxyActivity : BaseActivity() {
companion object {
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps"
}
private var adapter: PerAppProxyAdapter? = null private var adapter: PerAppProxyAdapter? = null
private var appsAll: List<AppInfo>? = null private var appsAll: List<AppInfo>? = null
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -52,7 +49,7 @@ class PerAppProxyActivity : BaseActivity() {
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL) val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
recycler_view.addItemDecoration(dividerItemDecoration) recycler_view.addItemDecoration(dividerItemDecoration)
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null) val blacklist = defaultSharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, null)
AppManagerUtil.rxLoadNetworkAppList(this) AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
@@ -141,15 +138,15 @@ class PerAppProxyActivity : BaseActivity() {
} }
}) })
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked -> switch_per_app_proxy.setOnCheckedChangeListener { _, isChecked ->
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked) defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
} }
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false) switch_per_app_proxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked -> switch_bypass_apps.setOnCheckedChangeListener { _, isChecked ->
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked) defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
} }
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false) switch_bypass_apps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
et_search.setOnEditorActionListener { v, actionId, event -> et_search.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) { if (actionId == EditorInfo.IME_ACTION_SEARCH) {
@@ -183,7 +180,7 @@ class PerAppProxyActivity : BaseActivity() {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
adapter?.let { adapter?.let {
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist) defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply()
} }
} }

View File

@@ -5,9 +5,9 @@ import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.support.v7.preference.PreferenceManager
import android.view.* import android.view.*
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.fragment_routing_settings.* import kotlinx.android.synthetic.main.fragment_routing_settings.*
import android.view.MenuInflater import android.view.MenuInflater
@@ -26,6 +26,8 @@ class RoutingSettingsFragment : Fragment() {
private const val REQUEST_SCAN_APPEND = 12 private const val REQUEST_SCAN_APPEND = 12
} }
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? { savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment // Inflate the layout for this fragment
@@ -40,10 +42,10 @@ class RoutingSettingsFragment : Fragment() {
return fragment return fragment
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onViewCreated(view, savedInstanceState)
val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "") val content = defaultSharedPreferences.getString(arguments!!.getString(routing_arg), "")
et_routing_content.text = Utils.getEditable(content!!) et_routing_content.text = Utils.getEditable(content!!)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@@ -57,7 +59,7 @@ class RoutingSettingsFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.save_routing -> { R.id.save_routing -> {
val content = et_routing_content.text.toString() val content = et_routing_content.text.toString()
activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content) defaultSharedPreferences.edit().putString(arguments!!.getString(routing_arg), content).apply()
activity?.toast(R.string.toast_success) activity?.toast(R.string.toast_success)
true true
} }

View File

@@ -23,8 +23,8 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
private var mScannerView: ZXingScannerView? = null private var mScannerView: ZXingScannerView? = null
public override fun onCreate(state: Bundle?) { public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(state) super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true) mScannerView?.setAutoFocus(true)

View File

@@ -1,152 +0,0 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.Editable
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import com.google.gson.Gson
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server2.*
import java.lang.Exception
class Server2Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server2)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
tv_content.text = Editable.Factory.getInstance().newEditable(defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + edit_guid, ""))
return true
}
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
return true
}
/**
* save server config
*/
fun saveServer(): Boolean {
val vmess = configs.vmess[edit_index]
vmess.remarks = et_remarks.text.toString()
if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks)
return false
}
try {
Gson().fromJson<Object>(tv_content.text.toString(), Object::class.java)
} catch (e: Exception) {
e.printStackTrace()
toast(R.string.toast_malformed_josn)
return false
}
if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
//update config
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
}
}
.show()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -1,175 +0,0 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server3.*
class Server3Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
private val securitys: Array<out String> by lazy {
resources.getStringArray(R.array.ss_securitys)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server3)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
et_address.text = Utils.getEditable(vmess.address)
et_port.text = Utils.getEditable(vmess.port.toString())
et_id.text = Utils.getEditable(vmess.id)
val security = Utils.arrayFind(securitys, vmess.security)
if (security >= 0) {
sp_security.setSelection(security)
}
return true
}
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
et_address.text = null
et_port.text = Utils.getEditable("10086")
et_id.text = null
sp_security.setSelection(0)
return true
}
/**
* save server config
*/
fun saveServer(): Boolean {
val vmess: AngConfig.VmessBean
if (edit_index >= 0) {
vmess = configs.vmess[edit_index]
} else {
vmess = AngConfig.VmessBean()
}
vmess.guid = edit_guid
vmess.remarks = et_remarks.text.toString()
vmess.address = et_address.text.toString()
vmess.port = Utils.parseInt(et_port.text.toString())
vmess.id = et_id.text.toString()
vmess.security = securitys[sp_security.selectedItemPosition]
if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks)
return false
}
if (TextUtils.isEmpty(vmess.address)) {
toast(R.string.server_lab_address3)
return false
}
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
toast(R.string.server_lab_port3)
return false
}
if (TextUtils.isEmpty(vmess.id)) {
toast(R.string.server_lab_id3)
return false
}
if (AngConfigManager.addShadowsocksServer(vmess, edit_index) == 0) {
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
}
}
.show()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -1,159 +0,0 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server4.*
class Server4Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server4)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
et_address.text = Utils.getEditable(vmess.address)
et_port.text = Utils.getEditable(vmess.port.toString())
return true
}
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
et_address.text = null
et_port.text = Utils.getEditable("10086")
return true
}
/**
* save server config
*/
fun saveServer(): Boolean {
val vmess: AngConfig.VmessBean
if (edit_index >= 0) {
vmess = configs.vmess[edit_index]
} else {
vmess = AngConfig.VmessBean()
}
vmess.guid = edit_guid
vmess.remarks = et_remarks.text.toString()
vmess.address = et_address.text.toString()
vmess.port = Utils.parseInt(et_port.text.toString())
if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks)
return false
}
if (TextUtils.isEmpty(vmess.address)) {
toast(R.string.server_lab_address3)
return false
}
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
toast(R.string.server_lab_port3)
return false
}
if (AngConfigManager.addSocksServer(vmess, edit_index) == 0) {
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
}
}
.show()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -5,50 +5,112 @@ import android.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
import com.v2ray.ang.dto.V2rayConfig.Companion.XTLS
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.ID_MAIN
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server.* import kotlinx.android.synthetic.main.activity_server_socks.*
import kotlinx.android.synthetic.main.activity_server_vmess.*
import kotlinx.android.synthetic.main.activity_server_vmess.et_address
import kotlinx.android.synthetic.main.activity_server_vmess.et_id
import kotlinx.android.synthetic.main.activity_server_vmess.et_path
import kotlinx.android.synthetic.main.activity_server_vmess.et_port
import kotlinx.android.synthetic.main.activity_server_vmess.et_remarks
import kotlinx.android.synthetic.main.activity_server_vmess.et_request_host
import kotlinx.android.synthetic.main.activity_server_vmess.sp_allow_insecure
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type_title
import kotlinx.android.synthetic.main.activity_server_vmess.sp_network
import kotlinx.android.synthetic.main.activity_server_vmess.sp_stream_security
class ServerActivity : BaseActivity() { class ServerActivity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1 private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy {
intent.getBooleanExtra("isRunning", false)
&& editGuid.isNotEmpty()
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
}
private val createConfigType by lazy {
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
} }
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
private val securitys: Array<out String> by lazy { private val securitys: Array<out String> by lazy {
resources.getStringArray(R.array.securitys) resources.getStringArray(R.array.securitys)
} }
private val shadowsocksSecuritys: Array<out String> by lazy {
resources.getStringArray(R.array.ss_securitys)
}
private val flows: Array<out String> by lazy {
resources.getStringArray(R.array.flows)
}
private val networks: Array<out String> by lazy { private val networks: Array<out String> by lazy {
resources.getStringArray(R.array.networks) resources.getStringArray(R.array.networks)
} }
private val headertypes: Array<out String> by lazy { private val tcpTypes: Array<out String> by lazy {
resources.getStringArray(R.array.headertypes) resources.getStringArray(R.array.header_type_tcp)
} }
private val streamsecuritys: Array<out String> by lazy { private val kcpAndQuicTypes: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecuritys) resources.getStringArray(R.array.header_type_kcp_and_quic)
}
private val grpcModes: Array<out String> by lazy {
resources.getStringArray(R.array.mode_type_grpc)
}
private val streamSecuritys: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecurityxs)
}
private val allowinsecures: Array<out String> by lazy {
resources.getStringArray(R.array.allowinsecures)
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server) title = getString(R.string.title_server)
if (edit_index >= 0) { val config = MmkvManager.decodeServerConfig(editGuid)
edit_guid = configs.vmess[edit_index].guid when(config?.configType ?: createConfigType) {
bindingServer(configs.vmess[edit_index]) EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
EConfigType.CUSTOM -> return
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
// EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
// EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
}
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val types = transportTypes(networks[position])
sp_header_type?.isEnabled = types.size > 1
val adapter = ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
sp_header_type?.adapter = adapter
sp_header_type_title?.text = if (networks[position] == "grpc")
getString(R.string.server_lab_mode_type) else
getString(R.string.server_lab_head_type)
config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails ->
sp_header_type.setSelection(Utils.arrayFind(types, transportDetails[0]))
et_request_host.text = Utils.getEditable(transportDetails[1])
et_path.text = Utils.getEditable(transportDetails[2])
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
// do nothing
}
}
if (config != null) {
bindingServer(config)
} else { } else {
clearServer() clearServer()
} }
@@ -58,33 +120,44 @@ class ServerActivity : BaseActivity() {
/** /**
* bingding seleced server config * bingding seleced server config
*/ */
fun bindingServer(vmess: AngConfig.VmessBean): Boolean { private fun bindingServer(config: ServerConfig): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks) val outbound = config.getProxyOutbound() ?: return false
val streamSetting = config.outboundBean?.streamSettings ?: return false
et_address.text = Utils.getEditable(vmess.address) et_remarks.text = Utils.getEditable(config.remarks)
et_port.text = Utils.getEditable(vmess.port.toString()) et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
et_id.text = Utils.getEditable(vmess.id) et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
et_alterId.text = Utils.getEditable(vmess.alterId.toString()) et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
val security = Utils.arrayFind(securitys, vmess.security) if (config.configType == EConfigType.SOCKS) {
et_security.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
} else if (config.configType == EConfigType.VLESS) {
et_security.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
if (flow >= 0) {
//sp_flow.setSelection(flow)
}
}
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security = Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
if (security >= 0) { if (security >= 0) {
sp_security.setSelection(security) sp_security?.setSelection(security)
}
val network = Utils.arrayFind(networks, vmess.network)
if (network >= 0) {
sp_network.setSelection(network)
} }
val headerType = Utils.arrayFind(headertypes, vmess.headerType) val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
if (headerType >= 0) {
sp_header_type.setSelection(headerType)
}
et_request_host.text = Utils.getEditable(vmess.requestHost)
et_path.text = Utils.getEditable(vmess.path)
val streamSecurity = Utils.arrayFind(streamsecuritys, vmess.streamSecurity)
if (streamSecurity >= 0) { if (streamSecurity >= 0) {
sp_stream_security.setSelection(streamSecurity) sp_stream_security?.setSelection(streamSecurity)
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
if (allowinsecure >= 0) {
sp_allow_insecure?.setSelection(allowinsecure)
}
et_request_host.text = Utils.getEditable(tlsSetting.serverName)
}
}
val network = Utils.arrayFind(networks, streamSetting.network)
if (network >= 0) {
sp_network?.setSelection(network)
} }
return true return true
} }
@@ -92,113 +165,179 @@ class ServerActivity : BaseActivity() {
/** /**
* clear or init server config * clear or init server config
*/ */
fun clearServer(): Boolean { private fun clearServer(): Boolean {
et_remarks.text = null et_remarks.text = null
et_address.text = null et_address.text = null
et_port.text = Utils.getEditable("10086") et_port.text = Utils.getEditable(DEFAULT_PORT.toString())
et_id.text = null et_id.text = null
et_alterId.text = Utils.getEditable("64") et_alterId?.text = Utils.getEditable("0")
sp_security.setSelection(0) sp_security?.setSelection(0)
sp_network.setSelection(0) sp_network?.setSelection(0)
sp_header_type.setSelection(0) sp_header_type?.setSelection(0)
et_request_host.text = null et_request_host?.text = null
et_path.text = null et_path?.text = null
sp_stream_security.setSelection(0) sp_stream_security?.setSelection(0)
sp_allow_insecure?.setSelection(0)
//et_security.text = null
//sp_flow?.setSelection(0)
return true return true
} }
/** /**
* save server config * save server config
*/ */
fun saveServer(): Boolean { private fun saveServer(): Boolean {
val vmess: AngConfig.VmessBean if (TextUtils.isEmpty(et_remarks.text.toString())) {
if (edit_index >= 0) {
vmess = configs.vmess[edit_index]
} else {
vmess = AngConfig.VmessBean()
}
vmess.guid = edit_guid
vmess.remarks = et_remarks.text.toString()
vmess.address = et_address.text.toString()
vmess.port = Utils.parseInt(et_port.text.toString())
vmess.id = et_id.text.toString()
vmess.alterId = Utils.parseInt(et_alterId.text.toString())
vmess.security = securitys[sp_security.selectedItemPosition]
vmess.network = networks[sp_network.selectedItemPosition]
vmess.headerType = headertypes[sp_header_type.selectedItemPosition]
vmess.requestHost = et_request_host.text.toString()
vmess.path = et_path.text.toString()
vmess.streamSecurity = streamsecuritys[sp_stream_security.selectedItemPosition]
if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks) toast(R.string.server_lab_remarks)
return false return false
} }
if (TextUtils.isEmpty(vmess.address)) { if (TextUtils.isEmpty(et_address.text.toString())) {
toast(R.string.server_lab_address) toast(R.string.server_lab_address)
return false return false
} }
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) { val port = Utils.parseInt(et_port.text.toString())
if (port <= 0) {
toast(R.string.server_lab_port) toast(R.string.server_lab_port)
return false return false
} }
if (TextUtils.isEmpty(vmess.id)) { val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
toast(R.string.server_lab_id) toast(R.string.server_lab_id)
return false return false
} }
if (TextUtils.isEmpty(vmess.alterId.toString()) || vmess.alterId < 0) { et_alterId?.let {
toast(R.string.server_lab_alterid) val alterId = Utils.parseInt(et_alterId.text.toString())
return false if (alterId < 0) {
toast(R.string.server_lab_alterid)
return false
}
} }
if (AngConfigManager.addServer(vmess, edit_index) == 0) { config.remarks = et_remarks.text.toString().trim()
AngConfigManager.genStoreV2rayConfigIfActive(edit_index) config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
toast(R.string.toast_success) saveVnext(vnext, port, config)
finish() }
return true config.outboundBean?.settings?.servers?.get(0)?.let { server ->
saveServers(server, port, config)
}
config.outboundBean?.streamSettings?.let {
saveStreamSettings(it, config)
}
MmkvManager.encodeServerConfig(editGuid, config)
toast(R.string.toast_success)
finish()
return true
}
private fun saveVnext(vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean, port: Int, config: ServerConfig) {
vnext.address = et_address.text.toString().trim()
vnext.port = port
vnext.users[0].id = et_id.text.toString().trim()
if (config.configType == EConfigType.VMESS) {
vnext.users[0].alterId = Utils.parseInt(et_alterId.text.toString())
vnext.users[0].security = securitys[sp_security.selectedItemPosition]
} else if (config.configType == EConfigType.VLESS) {
vnext.users[0].encryption = et_security.text.toString().trim()
if (streamSecuritys[sp_stream_security.selectedItemPosition] == XTLS) {
// vnext.users[0].flow = if (flows[sp_flow.selectedItemPosition].isBlank()) V2rayConfig.DEFAULT_FLOW
// else flows[sp_flow.selectedItemPosition]
} else {
vnext.users[0].flow = ""
}
}
}
private fun saveServers(server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean, port: Int, config: ServerConfig) {
server.address = et_address.text.toString().trim()
server.port = port
if (config.configType == EConfigType.SHADOWSOCKS) {
server.password = et_id.text.toString().trim()
server.method = shadowsocksSecuritys[sp_security.selectedItemPosition]
} else if (config.configType == EConfigType.SOCKS) {
if (TextUtils.isEmpty(et_security.text) && TextUtils.isEmpty(et_id.text)) {
server.users = null
} else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = et_security.text.toString().trim()
socksUsersBean.pass = et_id.text.toString().trim()
server.users = listOf(socksUsersBean)
}
} else if (config.configType == EConfigType.TROJAN) {
server.password = et_id.text.toString().trim()
}
}
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, config: ServerConfig) {
val network = if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK
val type = if (sp_header_type != null) transportTypes(network)[sp_header_type.selectedItemPosition] else "";
val requestHost = if (et_request_host != null) et_request_host.text.toString().trim() else ""
val path = if (et_path != null) et_path.text.toString().trim() else ""
var sni = streamSetting.populateTransportSettings(
transport = network,
headerType = type,
host = requestHost,
path = path,
seed = path,
quicSecurity = requestHost,
key = path,
mode = type,
serviceName = path
)
val allowInsecure = if (sp_allow_insecure == null || allowinsecures[sp_allow_insecure.selectedItemPosition].isBlank()) {
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
} else { } else {
toast(R.string.toast_failure) allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean()
return false }
val defaultTls = if (config.configType == EConfigType.TROJAN) V2rayConfig.TLS else ""
streamSetting.populateTlsSettings(
if (sp_stream_security != null) streamSecuritys[sp_stream_security.selectedItemPosition] else defaultTls,
allowInsecure,
sni
)
}
private fun transportTypes(network: String?): Array<out String> {
return if (network == "tcp") {
tcpTypes
} else if (network == "kcp" || network == "quic") {
kcpAndQuicTypes
} else if (network == "grpc") {
grpcModes
} else {
arrayOf("---")
} }
} }
/** /**
* save server config * save server config
*/ */
fun deleteServer(): Boolean { private fun deleteServer(): Boolean {
if (edit_index >= 0) { if (editGuid.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) { MmkvManager.removeServer(editGuid)
toast(R.string.toast_success) finish()
finish()
} else {
toast(R.string.toast_failure)
}
} }
.show() .show()
} else {
} }
return true return true
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu) menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config) val delButton = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config) val saveButton = menu?.findItem(R.id.save_config)
if (edit_index >= 0) { if (editGuid.isNotEmpty()) {
if (isRunning) { if (isRunning) {
if (edit_index == configs.index) { delButton?.isVisible = false
del_config?.isVisible = false saveButton?.isVisible = false
save_config?.isVisible = false
}
} }
} else { } else {
del_config?.isVisible = false delButton?.isVisible = false
} }
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)

View File

@@ -0,0 +1,139 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server_custom_config.*
import me.drakeet.support.toast.ToastCompat
class ServerCustomConfigActivity : BaseActivity() {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy {
intent.getBooleanExtra("isRunning", false)
&& editGuid.isNotEmpty()
&& editGuid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server_custom_config)
title = getString(R.string.title_server)
val config = MmkvManager.decodeServerConfig(editGuid)
if (config != null) {
bindingServer(config)
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
private fun bindingServer(config: ServerConfig): Boolean {
et_remarks.text = Utils.getEditable(config.remarks)
val raw = serverRawStorage?.decodeString(editGuid)
if (raw.isNullOrBlank()) {
tv_content.text = Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty())
} else {
tv_content.text = Utils.getEditable(raw)
}
return true
}
/**
* clear or init server config
*/
private fun clearServer(): Boolean {
et_remarks.text = null
return true
}
/**
* save server config
*/
private fun saveServer(): Boolean {
if (TextUtils.isEmpty(et_remarks.text.toString())) {
toast(R.string.server_lab_remarks)
return false
}
val v2rayConfig = try {
Gson().fromJson(tv_content.text.toString(), V2rayConfig::class.java)
} catch (e: Exception) {
e.printStackTrace()
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
return false
}
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
config.remarks = et_remarks.text.toString().trim()
config.fullConfig = v2rayConfig
MmkvManager.encodeServerConfig(editGuid, config)
serverRawStorage?.encode(editGuid, tv_content.text.toString())
toast(R.string.toast_success)
finish()
return true
}
/**
* save server config
*/
private fun deleteServer(): Boolean {
if (editGuid.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeServer(editGuid)
finish()
}
.show()
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
val delButton = menu?.findItem(R.id.del_config)
val saveButton = menu?.findItem(R.id.save_config)
if (editGuid.isNotEmpty()) {
if (isRunning) {
delButton?.isVisible = false
saveButton?.isVisible = false
}
} else {
delButton?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -4,40 +4,16 @@ import android.arch.lifecycle.ViewModelProviders
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v7.preference.* import android.support.v7.preference.*
import android.text.TextUtils
import android.view.View import android.view.View
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel import com.v2ray.ang.viewmodel.SettingsViewModel
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
companion object {
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
// const val PREF_SOCKS_PORT = "pref_socks_port"
// const val PREF_HTTP_PORT = "pref_http_port"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_MODE = "pref_routing_mode"
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
// const val PREF_DONATE = "pref_donate"
// const val PREF_LICENSES = "pref_licenses"
// const val PREF_FEEDBACK = "pref_feedback"
// const val PREF_TG_GROUP = "pref_tg_group"
// const val PREF_AUTO_RESTART = "pref_auto_restart"
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
}
private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) } private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) }
@@ -53,17 +29,21 @@ class SettingsActivity : BaseActivity() {
} }
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference } private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
private val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference } private val localDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) }
private val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference } private val fakeDns by lazy { findPreference(AppConfig.PREF_FAKE_DNS_ENABLED) }
private val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference } private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) }
private val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference } private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) }
private val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference } private val sppedEnabled by lazy { findPreference(AppConfig.PREF_SPEED_ENABLED) as CheckBoxPreference }
private val sniffingEnabled by lazy { findPreference(AppConfig.PREF_SNIFFING_ENABLED) as CheckBoxPreference }
private val proxySharing by lazy { findPreference(AppConfig.PREF_PROXY_SHARING) as CheckBoxPreference }
private val domainStrategy by lazy { findPreference(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
private val routingMode by lazy { findPreference(AppConfig.PREF_ROUTING_MODE) as ListPreference }
private val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference } private val forwardIpv6 by lazy { findPreference(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
private val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference } private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
private val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference } private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
private val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
@@ -71,7 +51,7 @@ class SettingsActivity : BaseActivity() {
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference } // val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference } // val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
private val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) } private val routingCustom: Preference by lazy { findPreference(AppConfig.PREF_ROUTING_CUSTOM) }
// val donate: Preference by lazy { findPreference(PREF_DONATE) } // val donate: Preference by lazy { findPreference(PREF_DONATE) }
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } // val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
@@ -81,7 +61,7 @@ class SettingsActivity : BaseActivity() {
private fun restartProxy() { private fun restartProxy() {
Utils.stopVService(requireContext()) Utils.stopVService(requireContext())
Utils.startVService(requireContext(), AngConfigManager.configs.index) V2RayServiceManager.startV2Ray(requireContext())
} }
private fun isRunning(): Boolean { private fun isRunning(): Boolean {
@@ -166,9 +146,21 @@ class SettingsActivity : BaseActivity() {
restartProxy() restartProxy()
true true
} }
localDns?.setOnPreferenceChangeListener{ _, any ->
updateLocalDns(any as Boolean)
true
}
localDnsPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) "10807" else nval
true
}
vpnDns?.setOnPreferenceChangeListener { _, any ->
vpnDns?.summary = any as String
true
}
mode.setOnPreferenceChangeListener { _, newValue -> mode.setOnPreferenceChangeListener { _, newValue ->
updatePerAppProxy(newValue.toString()) updateMode(newValue.toString())
true true
} }
mode.dialogLayoutResource = R.layout.preference_with_help_link mode.dialogLayoutResource = R.layout.preference_with_help_link
@@ -213,32 +205,43 @@ class SettingsActivity : BaseActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity) val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
updatePerAppProxy(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN")) updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "") var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "") domesticDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
if (remoteDns.summary == "") { if (TextUtils.isEmpty(remoteDnsString)) {
remoteDns.summary = AppConfig.DNS_AGENT remoteDnsString = AppConfig.DNS_AGENT
} }
if ( domesticDns.summary == "") { if ( domesticDns.summary == "") {
domesticDns.summary = AppConfig.DNS_DIRECT domesticDns.summary = AppConfig.DNS_DIRECT
} }
remoteDns.summary = remoteDnsString
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808") // socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "") // lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
} }
private fun updatePerAppProxy(mode: String?) { private fun updateMode(mode: String?) {
if (mode == "VPN") { val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
perAppProxy.isEnabled = true val vpn = mode == "VPN"
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity) perAppProxy.isEnabled = vpn
.getBoolean(PREF_PER_APP_PROXY, false) perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
} else { .getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
perAppProxy.isEnabled = false localDns?.isEnabled = vpn
perAppProxy.isChecked = false fakeDns?.isEnabled = vpn
localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn
if (vpn) {
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false))
} }
} }
private fun updateLocalDns(enabled: Boolean) {
fakeDns?.isEnabled = enabled
localDnsPort?.isEnabled = enabled
vpnDns?.isEnabled = !enabled
}
} }
fun onModeHelpClicked(view: View) { fun onModeHelpClicked(view: View) {

View File

@@ -5,10 +5,12 @@ import android.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_sub_edit.* import kotlinx.android.synthetic.main.activity_sub_edit.*
@@ -17,20 +19,17 @@ class SubEditActivity : BaseActivity() {
var del_config: MenuItem? = null var del_config: MenuItem? = null
var save_config: MenuItem? = null var save_config: MenuItem? = null
private lateinit var configs: AngConfig private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private var edit_index: Int = -1 //当前编辑的 private val editSubId by lazy { intent.getStringExtra("subId").orEmpty() }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub_edit) setContentView(R.layout.activity_sub_edit)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
title = getString(R.string.title_sub_setting) title = getString(R.string.title_sub_setting)
if (edit_index >= 0) { val json = subStorage?.decodeString(editSubId)
bindingServer(configs.subItem[edit_index]) if (!json.isNullOrBlank()) {
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
} else { } else {
clearServer() clearServer()
} }
@@ -40,7 +39,7 @@ class SubEditActivity : BaseActivity() {
/** /**
* bingding seleced server config * bingding seleced server config
*/ */
fun bindingServer(subItem: AngConfig.SubItemBean): Boolean { private fun bindingServer(subItem: SubscriptionItem): Boolean {
et_remarks.text = Utils.getEditable(subItem.remarks) et_remarks.text = Utils.getEditable(subItem.remarks)
et_url.text = Utils.getEditable(subItem.url) et_url.text = Utils.getEditable(subItem.url)
@@ -50,7 +49,7 @@ class SubEditActivity : BaseActivity() {
/** /**
* clear or init server config * clear or init server config
*/ */
fun clearServer(): Boolean { private fun clearServer(): Boolean {
et_remarks.text = null et_remarks.text = null
et_url.text = null et_url.text = null
@@ -60,12 +59,15 @@ class SubEditActivity : BaseActivity() {
/** /**
* save server config * save server config
*/ */
fun saveServer(): Boolean { private fun saveServer(): Boolean {
val subItem: AngConfig.SubItemBean val subItem: SubscriptionItem
if (edit_index >= 0) { val json = subStorage?.decodeString(editSubId)
subItem = configs.subItem[edit_index] var subId = editSubId
if (!json.isNullOrBlank()) {
subItem = Gson().fromJson(json, SubscriptionItem::class.java)
} else { } else {
subItem = AngConfig.SubItemBean() subId = Utils.getUuid()
subItem = SubscriptionItem()
} }
subItem.remarks = et_remarks.text.toString() subItem.remarks = et_remarks.text.toString()
@@ -80,32 +82,23 @@ class SubEditActivity : BaseActivity() {
return false return false
} }
if (AngConfigManager.addSubItem(subItem, edit_index) == 0) { subStorage?.encode(subId, Gson().toJson(subItem))
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
return true return true
} else {
toast(R.string.toast_failure)
return false
}
} }
/** /**
* save server config * save server config
*/ */
fun deleteServer(): Boolean { private fun deleteServer(): Boolean {
if (edit_index >= 0) { if (editSubId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm) AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeSubItem(edit_index) == 0) { MmkvManager.removeSubscription(editSubId)
toast(R.string.toast_success) finish()
finish()
} else {
toast(R.string.toast_failure)
}
} }
.show() .show()
} else {
} }
return true return true
} }
@@ -115,8 +108,7 @@ class SubEditActivity : BaseActivity() {
del_config = menu?.findItem(R.id.del_config) del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config) save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) { if (editSubId.isEmpty()) {
} else {
del_config?.isVisible = false del_config?.isVisible = false
} }

View File

@@ -1,15 +1,18 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.content.Intent import android.content.Intent
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.v2ray.ang.R import com.v2ray.ang.R
import kotlinx.android.synthetic.main.activity_sub_setting.* import kotlinx.android.synthetic.main.activity_sub_setting.*
import android.os.Bundle import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.util.MmkvManager
class SubSettingActivity : BaseActivity() { class SubSettingActivity : BaseActivity() {
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
private val adapter by lazy { SubSettingRecyclerAdapter(this) } private val adapter by lazy { SubSettingRecyclerAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -27,7 +30,8 @@ class SubSettingActivity : BaseActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
adapter.updateConfigList() subscriptions = MmkvManager.decodeSubscriptions()
adapter.notifyDataSetChanged()
} }
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@@ -40,14 +44,9 @@ class SubSettingActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.add_config -> { R.id.add_config -> {
startActivity(Intent(this, SubEditActivity::class.java) startActivity(Intent(this, SubEditActivity::class.java))
.putExtra("position", -1)
)
adapter.updateConfigList()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -7,36 +7,27 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.util.AngConfigManager
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.* import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() { class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
private var mActivity: SubSettingActivity = activity private var mActivity: SubSettingActivity = activity
private lateinit var configs: AngConfig
init { override fun getItemCount() = mActivity.subscriptions.size
updateConfigList()
}
override fun getItemCount() = configs.subItem.count()
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) { if (holder is MainViewHolder) {
val remarks = configs.subItem[position].remarks val subId = mActivity.subscriptions[position].first
val url = configs.subItem[position].url val subItem = mActivity.subscriptions[position].second
holder.name.text = subItem.remarks
holder.name.text = remarks holder.url.text = subItem.url
holder.url.text = url
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.layout_edit.setOnClickListener { holder.layout_edit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java) mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
.putExtra("position", position) .putExtra("subId", subId)
) )
} }
} else {
} }
} }
@@ -45,15 +36,6 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
.inflate(R.layout.item_recycler_sub_setting, parent, false)) .inflate(R.layout.item_recycler_sub_setting, parent, false))
} }
fun updateConfigList() {
configs = AngConfigManager.configs
notifyDataSetChanged()
}
// fun updateSelectedItem() {
// notifyItemChanged(configs.index)
// }
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) { class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {

View File

@@ -7,21 +7,23 @@ import android.widget.ArrayAdapter
import android.widget.ListView import android.widget.ListView
import java.util.ArrayList import java.util.ArrayList
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import android.content.Intent import android.content.Intent
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.zxing.WriterException import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MmkvManager
import kotlinx.android.synthetic.main.activity_tasker.* import kotlinx.android.synthetic.main.activity_tasker.*
class TaskerActivity : BaseActivity() { class TaskerActivity : BaseActivity() {
private var listview: ListView? = null private var listview: ListView? = null
private var lstData: ArrayList<String> = ArrayList() private var lstData: ArrayList<String> = ArrayList()
private var lstGuid: ArrayList<String> = ArrayList() private var lstGuid: ArrayList<String> = ArrayList()
private val serverStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tasker) setContentView(R.layout.activity_tasker)
@@ -30,9 +32,11 @@ class TaskerActivity : BaseActivity() {
lstData.add("Default") lstData.add("Default")
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID) lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
AngConfigManager.configs.vmess.forEach { serverStorage?.allKeys()?.forEach { key ->
lstData.add(it.remarks) MmkvManager.decodeServerConfig(key)?.let { config ->
lstGuid.add(it.guid) lstData.add(config.remarks)
lstGuid.add(key)
}
} }
val adapter = ArrayAdapter(this, val adapter = ArrayAdapter(this,
android.R.layout.simple_list_item_single_choice, lstData) android.R.layout.simple_list_item_single_choice, lstData)
@@ -75,12 +79,10 @@ class TaskerActivity : BaseActivity() {
val intent = Intent() val intent = Intent()
val remarks = lstData[position] val remarks = lstData[position]
var blurb = "" val blurb = if (switch_start_service.isChecked) {
"Start $remarks"
if (switch_start_service.isChecked) {
blurb = "Start $remarks"
} else { } else {
blurb = "Stop $remarks" "Stop $remarks"
} }
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle) intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
@@ -108,4 +110,3 @@ class TaskerActivity : BaseActivity() {
} }
} }

View File

@@ -31,7 +31,7 @@ object AppManagerUtil {
return apps return apps
} }
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create { fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
it.onNext(loadNetworkAppList(ctx)) it.onNext(loadNetworkAppList(ctx))
} }

View File

@@ -0,0 +1,148 @@
package com.v2ray.ang.util
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.dto.ServerAffiliationInfo
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.SubscriptionItem
object MmkvManager {
const val ID_MAIN = "MAIN"
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
const val ID_SERVER_RAW = "SERVER_RAW"
const val ID_SERVER_AFF = "SERVER_AFF"
const val ID_SUB = "SUB"
const val ID_SETTING = "SETTING"
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
fun decodeServerList(): MutableList<String> {
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
return if (json.isNullOrBlank()) {
mutableListOf()
} else {
Gson().fromJson(json, Array<String>::class.java).toMutableList()
}
}
fun decodeServerConfig(guid: String): ServerConfig? {
if (guid.isBlank()) {
return null
}
val json = serverStorage?.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return Gson().fromJson(json, ServerConfig::class.java)
}
fun encodeServerConfig(guid: String, config: ServerConfig): String {
val key = if (guid.isBlank()) {
Utils.getUuid()
} else {
guid
}
serverStorage?.encode(key, Gson().toJson(config))
val serverList= decodeServerList()
if (!serverList.contains(key)) {
serverList.add(key)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
mainStorage?.encode(KEY_SELECTED_SERVER, key)
}
}
return key
}
fun removeServer(guid: String) {
if (guid.isBlank()) {
return
}
if (mainStorage?.decodeString(KEY_SELECTED_SERVER) == guid) {
mainStorage?.remove(KEY_SELECTED_SERVER)
}
val serverList= decodeServerList()
serverList.remove(guid)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
serverStorage?.remove(guid)
serverAffStorage?.remove(guid)
}
fun removeServerViaSubid(subid: String) {
if (subid.isBlank()) {
return
}
serverStorage?.allKeys()?.forEach { key ->
decodeServerConfig(key)?.let { config ->
if (config.subscriptionId == subid) {
removeServer(key)
}
}
}
}
fun decodeServerAffiliationInfo(guid: String): ServerAffiliationInfo? {
if (guid.isBlank()) {
return null
}
val json = serverAffStorage?.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return Gson().fromJson(json, ServerAffiliationInfo::class.java)
}
fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
if (guid.isBlank()) {
return
}
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
aff.testDelayMillis = testResult
serverAffStorage?.encode(guid, Gson().toJson(aff))
}
fun clearAllTestDelayResults() {
serverAffStorage?.allKeys()?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff ->
aff.testDelayMillis = 0
serverAffStorage?.encode(key, Gson().toJson(aff))
}
}
}
fun importUrlAsSubscription(url: String): Int {
val subscriptions = decodeSubscriptions()
subscriptions.forEach {
if (it.second.url == url) {
return 0
}
}
val subItem = SubscriptionItem()
subItem.remarks = "import sub"
subItem.url = url
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
return 1
}
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
subStorage?.allKeys()?.forEach { key ->
val json = subStorage?.decodeString(key)
if (!json.isNullOrBlank()) {
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
}
}
subscriptions.sortedBy { (_, value) -> value.addedTime }
return subscriptions
}
fun removeSubscription(subid: String) {
subStorage?.remove(subid)
removeServerViaSubid(subid)
}
}

View File

@@ -15,28 +15,25 @@ import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.text.TextUtils
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.webkit.URLUtil import android.webkit.URLUtil
import com.v2ray.ang.AngApplication import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.responseLength import com.v2ray.ang.extension.responseLength
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.ui.SettingsActivity
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import me.dozen.dpreference.DPreference
import java.io.IOException import java.io.IOException
import java.net.* import java.net.*
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
object Utils { object Utils {
val tcpTestingSockets = ArrayList<Socket?>() private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val tcpTestingSockets = ArrayList<Socket?>()
/** /**
* convert string to editalbe for kotlin * convert string to editalbe for kotlin
@@ -125,41 +122,31 @@ object Utils {
/** /**
* get remote dns servers from preference * get remote dns servers from preference
*/ */
fun getRemoteDnsServers(defaultDPreference: DPreference): ArrayList<String> { fun getRemoteDnsServers(): List<String> {
val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, AppConfig.DNS_AGENT) val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
val ret = ArrayList<String>() val ret = remoteDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
if (!TextUtils.isEmpty(remoteDns)) { if (ret.isEmpty()) {
remoteDns return listOf(AppConfig.DNS_AGENT)
.split(",")
.forEach {
if (Utils.isPureIpAddress(it)) {
ret.add(it)
}
}
}
if (ret.size == 0) {
ret.add(AppConfig.DNS_AGENT)
} }
return ret return ret
} }
fun getVpnDnsServers(): List<String> {
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS)
?: settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS)
?: AppConfig.DNS_AGENT
return vpnDns.split(",").filter { isPureIpAddress(it) }
// allow empty, in that case dns will use system default
}
/** /**
* get remote dns servers from preference * get remote dns servers from preference
*/ */
fun getDomesticDnsServers(defaultDPreference: DPreference): ArrayList<String> { fun getDomesticDnsServers(): List<String> {
val domesticDns = defaultDPreference.getPrefString(SettingsActivity.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT) val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val ret = ArrayList<String>() val ret = domesticDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
if (!TextUtils.isEmpty(domesticDns)) { if (ret.isEmpty()) {
domesticDns return listOf(AppConfig.DNS_DIRECT)
.split(",")
.forEach {
if (Utils.isPureIpAddress(it)) {
ret.add(it)
}
}
}
if (ret.size == 0) {
ret.add(AppConfig.DNS_DIRECT)
} }
return ret return ret
} }
@@ -260,7 +247,7 @@ object Utils {
*/ */
fun isValidUrl(value: String?): Boolean { fun isValidUrl(value: String?): Boolean {
try { try {
if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) { if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true return true
} }
} catch (e: WriterException) { } catch (e: WriterException) {
@@ -271,8 +258,7 @@ object Utils {
} }
fun startVServiceFromToggle(context: Context): Boolean { fun startVServiceFromToggle(context: Context): Boolean {
val result = context.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
if (result.isBlank()) {
context.toast(R.string.app_tile_first_use) context.toast(R.string.app_tile_first_use)
return false return false
} }
@@ -280,26 +266,6 @@ object Utils {
return true return true
} }
/**
* startVService
*/
fun startVService(context: Context, guid: String): Boolean {
val index = AngConfigManager.getIndexViaGuid(guid)
context.v2RayApplication.curIndex=index
return startVService(context, index)
}
/**
* startVService
*/
fun startVService(context: Context, index: Int): Boolean {
if (AngConfigManager.setActiveServer(index) < 0) {
return false
}
V2RayServiceManager.startV2Ray(context)
return true
}
/** /**
* stopVService * stopVService
*/ */
@@ -399,8 +365,8 @@ object Utils {
/** /**
* readTextFromAssets * readTextFromAssets
*/ */
fun readTextFromAssets(app: AngApplication, fileName: String): String { fun readTextFromAssets(context: Context, fileName: String): String {
val content = app.assets.open(fileName).bufferedReader().use { val content = context.assets.open(fileName).bufferedReader().use {
it.readText() it.readText()
} }
return content return content
@@ -414,7 +380,7 @@ object Utils {
val command = "/system/bin/ping -c 3 $url" val command = "/system/bin/ping -c 3 $url"
val process = Runtime.getRuntime().exec(command) val process = Runtime.getRuntime().exec(command)
val allText = process.inputStream.bufferedReader().use { it.readText() } val allText = process.inputStream.bufferedReader().use { it.readText() }
if (!TextUtils.isEmpty(allText)) { if (allText.isNotBlank()) {
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19) val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray() val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
if (temps.count() > 0 && temps[0].length < 10) { if (temps.count() > 0 && temps[0].length < 10) {
@@ -430,19 +396,18 @@ object Utils {
/** /**
* tcping * tcping
*/ */
suspend fun tcping(url: String, port: Int): String { suspend fun tcping(url: String, port: Int): Long {
var time = -1L var time = -1L
for (k in 0 until 2) { for (k in 0 until 2) {
val one = socketConnectTime(url, port) val one = socketConnectTime(url, port)
if (!coroutineContext.isActive) { if (!coroutineContext.isActive) {
break break
} }
if (one != -1L ) if (one != -1L && (time == -1L || one < time)) {
if(time == -1L || one < time) {
time = one time = one
} }
} }
return time.toString() + "ms" return time
} }
fun socketConnectTime(url: String, port: Int): Long { fun socketConnectTime(url: String, port: Int): Long {

View File

@@ -4,166 +4,120 @@ import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.google.gson.* import com.google.gson.*
import com.v2ray.ang.AngApplication import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AngConfig.VmessBean
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.ui.SettingsActivity
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONArray
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import kotlin.collections.ArrayList import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
import kotlin.collections.LinkedHashSet
object V2rayConfigUtil { object V2rayConfigUtil {
private val requestObj: JsonObject by lazy { private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""", JsonObject::class.java) private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
}
// private val responseObj: JSONObject by lazy {
// JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""")
// }
data class Result(var status: Boolean, var content: String) data class Result(var status: Boolean, var content: String)
/** /**
* 生成v2ray的客户端配置文件 * 生成v2ray的客户端配置文件
*/ */
fun getV2rayConfig(app: AngApplication, vmess: VmessBean): Result { fun getV2rayConfig(context: Context, guid: String): Result {
var result = Result(false, "")
try { try {
//检查设置 val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
// if (config.index < 0 if (config.configType == EConfigType.CUSTOM) {
// || config.vmess.count() <= 0 val raw = serverRawStorage?.decodeString(guid)
// || config.index > config.vmess.count() - 1 val customConfig = if (raw.isNullOrBlank()) {
// ) { config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
// return result } else {
// } raw
}
if (vmess.configType == EConfigType.CUSTOM.value) { Log.d("V2rayConfigUtilGoLog", customConfig)
result = getV2rayConfigType2(app, vmess) return Result(true, customConfig)
} else {
result = getV2rayConfigType1(app, vmess)
} }
val outbound = config.getProxyOutbound() ?: return Result(false, "")
val result = getV2rayNonCustomConfig(context, outbound)
Log.d("V2rayConfigUtilGoLog", result.content) Log.d("V2rayConfigUtilGoLog", result.content)
return result return result
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return result return Result(false, "")
}
}
fun getCustomConfigServerOutbound(content: Context, guid: String): V2rayConfig.OutboundBean? {
val jsonConfig = content.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
if (TextUtils.isEmpty(jsonConfig)) {
return null
}
val v2rayConfig: V2rayConfig? = try {
Gson().fromJson(jsonConfig, V2rayConfig::class.java)
} catch (e: JsonSyntaxException) {
e.printStackTrace()
null
}
v2rayConfig?.outbounds?.forEach { outbound ->
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
outbound.protocol.equals(EConfigType.SOCKS.name, true)) {
return outbound
}
}
return null
}
/**
* 生成v2ray的客户端配置文件
*/
private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result {
val result = Result(false, "")
try {
//取得默认配置
val assets = Utils.readTextFromAssets(app, "v2ray_config.json")
if (TextUtils.isEmpty(assets)) {
return result
}
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
// if (v2rayConfig == null) {
// return result
// }
inbounds(vmess, v2rayConfig, app)
outbounds(vmess, v2rayConfig, app)
routing(vmess, v2rayConfig, app)
if (app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)) {
customLocalDns(vmess, v2rayConfig, app)
} else {
customRemoteDns(vmess, v2rayConfig, app)
}
val finalConfig = GsonBuilder().setPrettyPrinting().create().toJson(v2rayConfig)
result.status = true
result.content = finalConfig
return result
} catch (e: Exception) {
e.printStackTrace()
return result
} }
} }
/** /**
* 生成v2ray的客户端配置文件 * 生成v2ray的客户端配置文件
*/ */
private fun getV2rayConfigType2(app: AngApplication, vmess: VmessBean): Result { private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result {
val result = Result(false, "") val result = Result(false, "")
try { //取得默认配置
val guid = vmess.guid val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "") if (TextUtils.isEmpty(assets)) {
result.status = true
result.content = jsonConfig
parseDomainNameAndTag(app, jsonConfig)
return result
} catch (e: Exception) {
e.printStackTrace()
return result return result
} }
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
inbounds(v2rayConfig)
httpRequestObject(outbound)
v2rayConfig.outbounds[0] = outbound
routing(v2rayConfig)
fakedns(v2rayConfig)
dns(v2rayConfig)
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
customLocalDns(v2rayConfig)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
v2rayConfig.stats = null
v2rayConfig.policy = null
}
result.status = true
result.content = v2rayConfig.toPrettyPrinting()
return result
} }
/** /**
* *
*/ */
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
try { try {
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
v2rayConfig.inbounds.forEach { curInbound -> v2rayConfig.inbounds.forEach { curInbound ->
if (!app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) { if (!(settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) ?: false)) {
//bind all inbounds to localhost if the user requests //bind all inbounds to localhost if the user requests
curInbound.listen = "127.0.0.1" curInbound.listen = "127.0.0.1"
} }
} }
v2rayConfig.inbounds[0].port = 10808 v2rayConfig.inbounds[0].port = 10808
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808")) val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, "")) ?: false
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED)
?: true
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
if (!sniffAllTlsAndHttp) {
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
}
if (fakedns) {
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
}
// if (socksPort > 0) { //v2rayConfig.inbounds[1].port = httpPort
// v2rayConfig.inbounds[0].port = socksPort
// } // if (httpPort > 0) {
// if (lanconnPort > 0) {
// val httpCopy = v2rayConfig.inbounds[0].copy() // val httpCopy = v2rayConfig.inbounds[0].copy()
// httpCopy.port = lanconnPort // httpCopy.port = httpPort
// httpCopy.protocol = "http" // httpCopy.protocol = "http"
// v2rayConfig.inbounds.add(httpCopy) // v2rayConfig.inbounds.add(httpCopy)
// } // }
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SNIFFING_ENABLED, true)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
@@ -171,230 +125,39 @@ object V2rayConfigUtil {
return true return true
} }
/** private fun fakedns(v2rayConfig: V2rayConfig) {
* vmess协议服务器配置 if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
*/ v2rayConfig.fakedns = V2rayConfig.FakednsBean()
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
try { it.settings?.domainStrategy = "UseIP"
val outbound = v2rayConfig.outbounds[0]
val configType = EConfigType.fromInt(vmess.configType)
if (configType != null) {
outbound.protocol = configType.name.toLowerCase()
} }
when (configType) {
EConfigType.VMESS -> {
outbound.settings?.servers = null
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
vnext?.address = vmess.address
vnext?.port = vmess.port
val user = vnext?.users?.get(0)
user?.id = vmess.id
user?.alterId = vmess.alterId
user?.security = vmess.security
user?.level = 8
//Mux
val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false)
outbound.mux?.enabled = muxEnabled
//远程服务器底层传输配置
outbound.streamSettings = boundStreamSettings(vmess)
}
EConfigType.SHADOWSOCKS -> {
outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0)
server?.address = vmess.address
server?.method = vmess.security
server?.ota = false
server?.password = vmess.id
server?.port = vmess.port
server?.level = 8
//Mux
outbound.mux?.enabled = false
}
EConfigType.SOCKS -> {
outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0)
server?.address = vmess.address
server?.port = vmess.port
//Mux
outbound.mux?.enabled = false
}
}
val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
String.format("[%s]:%s", vmess.address, vmess.port)
} else {
String.format("%s:%s", vmess.address, vmess.port)
}
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, serverDomain)
val tags = LinkedHashSet<String>()
v2rayConfig.outbounds.forEach {
if (!TextUtils.isEmpty(it.tag)) {
tags.add(it.tag)
}
}
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
} catch (e: Exception) {
e.printStackTrace()
return false
} }
return true
}
/**
* 远程服务器底层传输配置
*/
private fun boundStreamSettings(vmess: VmessBean): V2rayConfig.OutboundBean.StreamSettingsBean {
val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null, null)
try {
//远程服务器底层传输配置
streamSettings.network = vmess.network
streamSettings.security = vmess.streamSecurity
//streamSettings
when (streamSettings.network) {
"kcp" -> {
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
kcpsettings.mtu = 1350
kcpsettings.tti = 50
kcpsettings.uplinkCapacity = 12
kcpsettings.downlinkCapacity = 100
kcpsettings.congestion = false
kcpsettings.readBufferSize = 1
kcpsettings.writeBufferSize = 1
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
kcpsettings.header.type = vmess.headerType
streamSettings.kcpSettings = kcpsettings
}
"ws" -> {
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
if (!TextUtils.isEmpty(host)) {
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean.HeadersBean()
wssettings.headers.Host = host
}
if (!TextUtils.isEmpty(path)) {
wssettings.path = path
}
streamSettings.wsSettings = wssettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true
if (!TextUtils.isEmpty(host)) {
tlssettings.serverName = host
}
streamSettings.tlsSettings = tlssettings
}
"h2" -> {
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
if (!TextUtils.isEmpty(host)) {
httpsettings.host = host.split(",").map { it.trim() }
}
httpsettings.path = path
streamSettings.httpSettings = httpsettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true
streamSettings.tlsSettings = tlssettings
}
"quic" -> {
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
quicsettings.security = host
quicsettings.key = path
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean.HeaderBean()
quicsettings.header.type = vmess.headerType
streamSettings.quicSettings = quicsettings
}
else -> {
//tcp带http伪装
if (vmess.headerType == "http") {
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
tcpSettings.header.type = vmess.headerType
// if (requestObj.has("headers")
// || requestObj.optJSONObject("headers").has("Pragma")) {
// val arrHost = ArrayList<String>()
// vmess.requestHost
// .split(",")
// .forEach {
// arrHost.add(it)
// }
// requestObj.optJSONObject("headers")
// .put("Host", arrHost)
//
// }
if (!TextUtils.isEmpty(vmess.requestHost)) {
val arrHost = ArrayList<String>()
vmess.requestHost
.split(",")
.forEach {
arrHost.add("\"$it\"")
}
requestObj.getAsJsonObject("headers")
.add("Host", Gson().fromJson(arrHost.toString(), JsonArray::class.java))
}
if (!TextUtils.isEmpty(vmess.path)) {
val arrPath = ArrayList<String>()
vmess.path
.split(",")
.forEach {
arrPath.add("\"$it\"")
}
requestObj.add("path", Gson().fromJson(arrPath.toString(), JsonArray::class.java))
}
tcpSettings.header.request = requestObj
//tcpSettings.header.response = responseObj
streamSettings.tcpSettings = tcpSettings
}
}
}
} catch (e: Exception) {
e.printStackTrace()
return streamSettings
}
return streamSettings
} }
/** /**
* routing * routing
*/ */
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { private fun routing(v2rayConfig: V2rayConfig): Boolean {
try { try {
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig) routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig) ?: "", AppConfig.TAG_AGENT, v2rayConfig)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig) routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", AppConfig.TAG_DIRECT, v2rayConfig)
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "", AppConfig.TAG_BLOCKED, v2rayConfig)
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch") v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0") ?: "IPIfNonMatch"
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
// Hardcode googleapis.cn // Hardcode googleapis.cn
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean( val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
type = "field", type = "field",
outboundTag = AppConfig.TAG_AGENT, outboundTag = AppConfig.TAG_AGENT,
domain = arrayListOf("domain:googleapis.cn") domain = arrayListOf("domain:googleapis.cn")
) )
when (routingMode) { when (routingMode) {
"0" -> {
}
"1" -> { "1" -> {
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig) routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
} }
@@ -459,18 +222,18 @@ object V2rayConfigUtil {
rulesIP.ip = ArrayList<String>() rulesIP.ip = ArrayList<String>()
userRule.split(",").map { it.trim() }.forEach { userRule.split(",").map { it.trim() }.forEach {
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) { if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
rulesIP.ip?.add(it) rulesIP.ip?.add(it)
} else if (it.isNotEmpty()) } else if (it.isNotEmpty())
// if (Utils.isValidUrl(it) // if (Utils.isValidUrl(it)
// || it.startsWith("geosite:") // || it.startsWith("geosite:")
// || it.startsWith("regexp:") // || it.startsWith("regexp:")
// || it.startsWith("domain:") // || it.startsWith("domain:")
// || it.startsWith("full:")) // || it.startsWith("full:"))
{ {
rulesDomain.domain?.add(it) rulesDomain.domain?.add(it)
} }
} }
if (rulesDomain.domain?.size!! > 0) { if (rulesDomain.domain?.size!! > 0) {
v2rayConfig.routing.rules.add(rulesDomain) v2rayConfig.routing.rules.add(rulesDomain)
} }
@@ -496,33 +259,105 @@ object V2rayConfigUtil {
/** /**
* Custom Dns * Custom Dns
*/ */
private fun customLocalDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean { private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
val geositeCn = arrayListOf("geosite:cn")
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "")
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "")
// fakedns with all domains to make it always top priority
v2rayConfig.dns.servers?.add(0,
V2rayConfig.DnsBean.ServersBean(address = "fakedns", domains = geositeCn.plus(proxyDomain).plus(directDomain)))
}
// DNS inbound对象
val remoteDns = Utils.getRemoteDnsServers()
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = if (remoteDns.first().startsWith("https")) "1.1.1.1" else remoteDns.first(),
port = 53,
network = "tcp,udp")
//val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT) ?: AppConfig.PORT_LOCAL_DNS)
v2rayConfig.inbounds.add(
V2rayConfig.InboundBean(
tag = "dns-in",
port = 10807,
listen = "127.0.0.1",
protocol = "dokodemo-door",
settings = dnsInboundSettings,
sniffing = null))
}
// DNS outbound对象
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
v2rayConfig.outbounds.add(
V2rayConfig.OutboundBean(
protocol = "dns",
tag = "dns-out",
settings = null,
streamSettings = null,
mux = null))
}
// DNS routing tag
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out",
domain = null)
)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
private fun dns(v2rayConfig: V2rayConfig): Boolean {
try { try {
val hosts = mutableMapOf<String, String>() val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>() val servers = ArrayList<Any>()
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference) val remoteDns = Utils.getRemoteDnsServers()
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "")
remoteDns.forEach { remoteDns.forEach {
servers.add(it) servers.add(it)
} }
if (proxyDomain.size > 0) {
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference) servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, proxyDomain, null))
val agDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
if (agDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain))
} }
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, "")) // domestic DNS
if (dirDomain.size > 0) { val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain)) ?: "")
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
if (directDomain.size > 0 || routingMode == "2" || routingMode == "3") {
val domesticDns = Utils.getDomesticDnsServers()
val geositeCn = arrayListOf("geosite:cn")
val geoipCn = arrayListOf("geoip:cn")
if (directDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
}
if (routingMode == "2" || routingMode == "3") {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
}
if (!domesticDns.first().startsWith("https")) {
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null)
)
}
} }
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0") val blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
if (routingMode == "2" || routingMode == "3") { ?: "")
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, arrayListOf("geosite:cn")))
}
val blkDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
if (blkDomain.size > 0) { if (blkDomain.size > 0) {
hosts.putAll(blkDomain.map { it to "127.0.0.1" }) hosts.putAll(blkDomain.map { it to "127.0.0.1" })
} }
@@ -535,58 +370,37 @@ object V2rayConfigUtil {
servers = servers, servers = servers,
hosts = hosts) hosts = hosts)
// DNS inbound对象
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = remoteDns.first(),
port = 53,
network = "tcp,udp")
v2rayConfig.inbounds.add(
V2rayConfig.InboundBean(
tag = "dns-in",
port = 10807,
listen = "127.0.0.1",
protocol = "dokodemo-door",
settings = dnsInboundSettings,
sniffing = null))
}
// DNS outbound对象
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
v2rayConfig.outbounds.add(
V2rayConfig.OutboundBean(
protocol = "dns",
tag = "dns-out",
settings = null,
streamSettings = null,
mux = null))
}
// DNS routing // DNS routing
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean( if (!remoteDns.first().startsWith("https")) {
type = "field", v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
outboundTag = AppConfig.TAG_DIRECT, type = "field",
port = "53", outboundTag = AppConfig.TAG_AGENT,
ip = domesticDns, port = "53",
domain = null) ip = arrayListOf(remoteDns.first()),
) domain = null)
)
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean( private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean {
type = "field", try {
outboundTag = AppConfig.TAG_AGENT, if (outbound.streamSettings?.network == DEFAULT_NETWORK
port = "53", && outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
ip = remoteDns, val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
domain = null) val Host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
)
// DNS routing tag val requestString: String by lazy {
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean( """{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
type = "field", }
inboundTag = arrayListOf<String>("dns-in"), outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(requestString, V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java)
outboundTag = "dns-out", outbound.streamSettings?.tcpSettings?.header?.request?.path = path!!
domain = null) outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = Host!!
) }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -595,115 +409,4 @@ object V2rayConfigUtil {
return true return true
} }
/**
* Custom Remote Dns
*/
private fun customRemoteDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
try {
val servers = ArrayList<Any>()
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
servers.add(it)
}
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
/**
* is valid config
*/
fun isValidConfig(conf: String): Boolean {
try {
val jObj = JSONObject(conf)
var hasBound = false
//hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound"))
hasBound = (jObj.has("outbounds")) or (jObj.has("outbound"))
return hasBound
} catch (e: JSONException) {
return false
}
}
private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
try {
val jObj = JSONObject(jsonConfig)
var domainName = ""
val tags = LinkedHashSet<String>()
if (jObj.has("outbound")) {
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
domainName = domain
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
}
}
if (jObj.has("outbounds")) {
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outbounds").getJSONObject(i))
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
domainName = domain
}
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
}
}
}
if (jObj.has("outboundDetour")) {
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outboundDetour").getJSONObject(i))
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
domainName = domain
}
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
}
}
}
if (!TextUtils.isEmpty(domainName)) {
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
}
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun parseDomainNameAndTag(outbound: JSONObject): Pair<String, String> {
val tag = if (outbound.has("tag")) {
outbound.getString("tag")
} else {
""
}
try {
if (outbound.has("settings")) {
val vnext: JSONArray?
if (outbound.optJSONObject("settings").has("vnext")) {
// vmess
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
} else if (outbound.optJSONObject("settings").has("servers")) {
// shadowsocks or socks
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
} else {
return Pair("", tag)
}
for (i in 0..(vnext.length() - 1)) {
val item = vnext.getJSONObject(i)
val address = item.getString("address")
val port = item.getString("port")
if(Utils.isIpv6Address(address)) {
return Pair(String.format("[%s]:%s", address, port), tag)
} else {
return Pair(String.format("%s:%s", address, port), tag)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return Pair("", tag)
}
} }

View File

@@ -8,18 +8,28 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.util.Log import android.util.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.*
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
class MainViewModel(application: Application) : AndroidViewModel(application) { class MainViewModel(application: Application) : AndroidViewModel(application) {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
var serverList = MmkvManager.decodeServerList()
private set
val serversCache = ConcurrentHashMap<String, ServerConfig>()
val isRunning by lazy { MutableLiveData<Boolean>() } val isRunning by lazy { MutableLiveData<Boolean>() }
val updateListAction by lazy { MutableLiveData<Int>() } val updateListAction by lazy { MutableLiveData<Int>() }
val updateTestResultAction by lazy { MutableLiveData<String>() } val updateTestResultAction by lazy { MutableLiveData<String>() }
@@ -34,31 +44,67 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
override fun onCleared() { override fun onCleared() {
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver) getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
Utils.closeAllTcpSockets()
Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared") Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared")
super.onCleared() super.onCleared()
} }
fun reloadServerList() {
serverList = MmkvManager.decodeServerList()
updateCache()
updateListAction.value = -1
}
fun removeServer(guid: String) {
serverList.remove(guid)
MmkvManager.removeServer(guid)
}
fun appendCustomConfigServer(server: String) {
val config = ServerConfig.create(EConfigType.CUSTOM)
config.remarks = System.currentTimeMillis().toString()
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server)
serverList.add(key)
serversCache[key] = config
}
fun swapServer(fromPosition: Int, toPosition: Int) {
Collections.swap(serverList, fromPosition, toPosition)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
}
fun updateCache() {
serversCache.clear()
GlobalScope.launch(Dispatchers.Default) {
serverList.forEach { guid ->
MmkvManager.decodeServerConfig(guid)?.let {
serversCache[guid] = it
}
}
}
}
fun testAllTcping() { fun testAllTcping() {
tcpingTestScope.coroutineContext[Job]?.cancelChildren() tcpingTestScope.coroutineContext[Job]?.cancelChildren()
Utils.closeAllTcpSockets() Utils.closeAllTcpSockets()
for (k in 0 until AngConfigManager.configs.vmess.count()) { MmkvManager.clearAllTestDelayResults()
AngConfigManager.configs.vmess[k].testResult = "" updateListAction.value = -1 // update all
updateListAction.value = -1 // update all
} getApplication<AngApplication>().toast(R.string.connection_test_testing)
for (k in 0 until AngConfigManager.configs.vmess.count()) { for (guid in serverList) {
var serverAddress = AngConfigManager.configs.vmess[k].address serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?.getProxyOutbound()?.let { outbound ->
var serverPort = AngConfigManager.configs.vmess[k].port val serverAddress = outbound.getServerAddress()
if (AngConfigManager.configs.vmess[k].configType == EConfigType.CUSTOM.value) { val serverPort = outbound.getServerPort()
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(getApplication(), if (serverAddress != null && serverPort != null) {
AngConfigManager.configs.vmess[k].guid) ?: continue tcpingTestScope.launch {
serverAddress = serverOutbound.getServerAddress() ?: continue val testResult = Utils.tcping(serverAddress, serverPort)
serverPort = serverOutbound.getServerPort() ?: continue launch(Dispatchers.Main) {
} MmkvManager.encodeServerTestDelayMillis(guid, testResult)
tcpingTestScope.launch { updateListAction.value = serverList.indexOf(guid)
AngConfigManager.configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing }
it.testResult = Utils.tcping(serverAddress, serverPort)
launch(Dispatchers.Main) {
updateListAction.value = k
} }
} }
} }

View File

@@ -5,13 +5,14 @@ import android.arch.lifecycle.AndroidViewModel
import android.content.SharedPreferences import android.content.SharedPreferences
import android.support.v7.preference.PreferenceManager import android.support.v7.preference.PreferenceManager
import android.util.Log import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.ui.SettingsActivity.Companion import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.AngConfigManager
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class SettingsViewModel(application: Application) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener { class SettingsViewModel(application: Application) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
fun startListenPreferenceChange() { fun startListenPreferenceChange() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).registerOnSharedPreferenceChangeListener(this) PreferenceManager.getDefaultSharedPreferences(getApplication()).registerOnSharedPreferenceChangeListener(this)
} }
@@ -25,21 +26,31 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key") Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key")
when(key) { when(key) {
Companion.PREF_SNIFFING_ENABLED, AppConfig.PREF_MODE,
Companion.PREF_PROXY_SHARING, AppConfig.PREF_VPN_DNS,
Companion.PREF_LOCAL_DNS_ENABLED, AppConfig.PREF_REMOTE_DNS,
Companion.PREF_REMOTE_DNS, AppConfig.PREF_DOMESTIC_DNS,
Companion.PREF_DOMESTIC_DNS, AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
Companion.PREF_ROUTING_DOMAIN_STRATEGY, AppConfig.PREF_ROUTING_MODE,
Companion.PREF_ROUTING_MODE,
AppConfig.PREF_V2RAY_ROUTING_AGENT, AppConfig.PREF_V2RAY_ROUTING_AGENT,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED, AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> { AppConfig.PREF_V2RAY_ROUTING_DIRECT, -> {
GlobalScope.launch { settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
if (!AngConfigManager.genStoreV2rayConfig()) { }
Log.d(AppConfig.ANG_PACKAGE, "$key changed but generate full configuration failed!") AppConfig.PREF_SPEED_ENABLED,
} AppConfig.PREF_PROXY_SHARING,
} AppConfig.PREF_LOCAL_DNS_ENABLED,
AppConfig.PREF_FAKE_DNS_ENABLED,
AppConfig.PREF_FORWARD_IPV6,
AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS, -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
}
AppConfig.PREF_SNIFFING_ENABLED, -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
}
AppConfig.PREF_PER_APP_PROXY_SET -> {
settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
} }
} }
} }

View File

@@ -80,11 +80,51 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height" android:layout_marginTop="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height" android:orientation="vertical">
android:orientation="vertical" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="User(Optional)" />
<EditText
android:id="@+id/et_security"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password(Optional)" />
<EditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="textPassword" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -143,28 +143,28 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/server_lab_network" /> android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner <Spinner
android:id="@+id/sp_network" android:id="@+id/sp_network"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" android:layout_height="@dimen/edit_height"
android:entries="@array/networks" /> android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -174,6 +174,7 @@
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" /> android:text="@string/server_lab_head_type" />
@@ -181,8 +182,7 @@
<Spinner <Spinner
android:id="@+id/sp_header_type" android:id="@+id/sp_header_type"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/edit_height" android:layout_height="@dimen/edit_height" />
android:entries="@array/headertypes" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -241,6 +241,28 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_allow_insecure" />
<Spinner
android:id="@+id/sp_allow_insecure"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/allowinsecures" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -249,4 +271,4 @@
android:orientation="vertical" /> android:orientation="vertical" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -6,6 +6,8 @@
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string> <string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
<string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">数据迁移成功!</string>
<string name="migration_fail">数据迁移失败啦!</string>
<!-- Notifications --> <!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string> <string name="notification_action_stop_v2ray">停止</string>
@@ -41,10 +43,11 @@
<string name="server_lab_network">传输协议(network)</string> <string name="server_lab_network">传输协议(network)</string>
<string name="server_lab_more_function">功能设置(不清楚则保持默认值)</string> <string name="server_lab_more_function">功能设置(不清楚则保持默认值)</string>
<string name="server_lab_head_type">伪装类型(type)</string> <string name="server_lab_head_type">伪装类型(type)</string>
<string name="server_lab_request_host">伪装域名host(host/ws host/h2 host)/QUIC 加密方式</string> <string name="server_lab_mode_type">gRPC 传输模式 (mode)</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥</string> <string name="server_lab_request_host">伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式</string>
<string name="server_lab_stream_security">底层传输安全</string> <string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName</string>
<string name="server_lab_allow_insecure">allowInsecure</string> <string name="server_lab_stream_security">底层传输安全(tls)</string>
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
<string name="server_lab_address3">服务器地址</string> <string name="server_lab_address3">服务器地址</string>
<string name="server_lab_port3">服务器端口</string> <string name="server_lab_port3">服务器端口</string>
<string name="server_lab_id3">密码</string> <string name="server_lab_id3">密码</string>
@@ -74,7 +77,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">设置</string> <string name="title_settings">设置</string>
<string name="title_advanced">进阶设置</string> <string name="title_advanced">进阶设置</string>
<string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用代理</string> <string name="title_pref_per_app_proxy">分应用代理</string>
<string name="summary_pref_per_app_proxy">分应用代理仅支持 Android 5.0 Lollipop 及更高</string> <string name="summary_pref_per_app_proxy">分应用代理仅支持 Android 5.0 Lollipop 及更高</string>
@@ -85,10 +88,13 @@
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string> <string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
<string name="title_pref_sniffing_enabled">启用流量探测</string> <string name="title_pref_sniffing_enabled">启用流量探测</string>
<string name="summary_pref_sniffing_enabled">流量探测</string> <string name="summary_pref_sniffing_enabled">流量探测域名 (默认启用)</string>
<string name="title_pref_local_dns_enabled">启用本地DNS</string> <string name="title_pref_local_dns_enabled">启用本地DNS</string>
<string name="summary_pref_local_dns_enabled">DNS由本地设备的v2Ray-core处理测试</string> <string name="summary_pref_local_dns_enabled">DNS 请求导入 core 由 DNS 模块处理(推荐启用 如果需要路由绕过局域网及大陆地址)</string>
<string name="title_pref_fake_dns_enabled">启用虚拟DNS</string>
<string name="summary_pref_fake_dns_enabled">本地返回虚构解析结果 (减低延时 但个别应用可能无法使用)</string>
<string name="title_pref_forward_ipv6">IPv6 路由</string> <string name="title_pref_forward_ipv6">IPv6 路由</string>
<string name="summary_pref_forward_ipv6">转发 IPv6 流量到远程服务器(测试)</string> <string name="summary_pref_forward_ipv6">转发 IPv6 流量到远程服务器(测试)</string>
@@ -105,7 +111,9 @@
<string name="title_pref_remote_dns">远程DNS (可选)</string> <string name="title_pref_remote_dns">远程DNS (可选)</string>
<string name="summary_pref_remote_dns">DNS</string> <string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_domestic_dns">境内DNS (可选本地DNS模式下生效)</string> <string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5代理端口</string> <string name="title_pref_socks_port">SOCKS5代理端口</string>
@@ -114,6 +122,9 @@
<string name="title_pref_http_port">HTTP代理端口</string> <string name="title_pref_http_port">HTTP代理端口</string>
<string name="summary_pref_http_port">HTTP代理端口</string> <string name="summary_pref_http_port">HTTP代理端口</string>
<string name="title_pref_local_dns_port">本地DNS端口</string>
<string name="summary_pref_local_dns_port">本地DNS端口</string>
<string name="title_pref_feedback">反馈</string> <string name="title_pref_feedback">反馈</string>
<string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string> <string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
<string name="summary_pref_tg_group">加入Telegram Group</string> <string name="summary_pref_tg_group">加入Telegram Group</string>

View File

@@ -6,6 +6,8 @@
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string> <string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
<string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">數據遷移成功!</string>
<string name="migration_fail">數據遷移失敗啦!</string>
<!-- Notifications --> <!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string> <string name="notification_action_stop_v2ray">停止</string>
@@ -41,10 +43,11 @@
<string name="server_lab_network">網路</string> <string name="server_lab_network">網路</string>
<string name="server_lab_more_function">更多功能</string> <string name="server_lab_more_function">更多功能</string>
<string name="server_lab_head_type">標頭類型</string> <string name="server_lab_head_type">標頭類型</string>
<string name="server_lab_request_host">要求主機(host/ws host/h2 host)/QUIC加密方式</string> <string name="server_lab_mode_type">gRPC 傳輸模式 (mode)</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰</string> <string name="server_lab_request_host">要求主機(host)(host/ws host/h2 host)/QUIC加密方式</string>
<string name="server_lab_stream_security">傳輸層安全性</string> <string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰/kcp seed/gRPC serviceName</string>
<string name="server_lab_allow_insecure">allowInsecure</string> <string name="server_lab_stream_security">傳輸層安全性(tls)</string>
<string name="server_lab_allow_insecure">跳過證書驗證(allowInsecure)</string>
<string name="server_lab_address3">伺服器位址</string> <string name="server_lab_address3">伺服器位址</string>
<string name="server_lab_port3">伺服器埠</string> <string name="server_lab_port3">伺服器埠</string>
<string name="server_lab_id3">密碼</string> <string name="server_lab_id3">密碼</string>
@@ -75,7 +78,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">設定</string> <string name="title_settings">設定</string>
<string name="title_advanced">進階設定</string> <string name="title_advanced">進階設定</string>
<string name="title_vpn_settings">VPN 設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string> <string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
<string name="summary_pref_per_app_proxy">Proxy 個別應用程式模式只支援 Android 5.0 (Lollipop) 或更高</string> <string name="summary_pref_per_app_proxy">Proxy 個別應用程式模式只支援 Android 5.0 (Lollipop) 或更高</string>
@@ -86,10 +89,13 @@
<string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖標顯示流量的路由情況</string> <string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖標顯示流量的路由情況</string>
<string name="title_pref_sniffing_enabled">啟用流量探測</string> <string name="title_pref_sniffing_enabled">啟用流量探測</string>
<string name="summary_pref_sniffing_enabled">流量探測</string> <string name="summary_pref_sniffing_enabled">流量探測域名 (默認啟用)</string>
<string name="title_pref_local_dns_enabled">啟用本地DNS</string> <string name="title_pref_local_dns_enabled">啟用本地DNS</string>
<string name="summary_pref_local_dns_enabled">DNS請求由本地的v2ray core處理測試</string> <string name="summary_pref_local_dns_enabled">DNS 請求導入 core 由 DNS 模塊處理(推薦啟用 如果需要路由略過局域網及中國大陸</string>
<string name="title_pref_fake_dns_enabled">啟用虛擬DNS</string>
<string name="summary_pref_fake_dns_enabled">本地返回虛構解析結果 (減低延時 但個別應用可能無法使用)</string>
<string name="title_pref_forward_ipv6">IPv6 路由</string> <string name="title_pref_forward_ipv6">IPv6 路由</string>
<string name="summary_pref_forward_ipv6">向遠端重新導向 IPv6 流量(測試)</string> <string name="summary_pref_forward_ipv6">向遠端重新導向 IPv6 流量(測試)</string>
@@ -106,7 +112,9 @@
<string name="title_pref_remote_dns">遠端DNS (可選)</string> <string name="title_pref_remote_dns">遠端DNS (可選)</string>
<string name="summary_pref_remote_dns">DNS</string> <string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_domestic_dns">境内DNS (可選僅本地DNS模式下生效)</string> <string name="title_pref_vpn_dns">VPN DNS (僅支持 IPv4/v6)</string>
<string name="title_pref_domestic_dns">境内DNS (可選)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5代理連接埠</string> <string name="title_pref_socks_port">SOCKS5代理連接埠</string>
@@ -115,6 +123,8 @@
<string name="title_pref_http_port">HTTP代理連接埠</string> <string name="title_pref_http_port">HTTP代理連接埠</string>
<string name="summary_pref_http_port">HTTP代理連接埠</string> <string name="summary_pref_http_port">HTTP代理連接埠</string>
<string name="title_pref_local_dns_port">本地DNS端口</string>
<string name="summary_pref_local_dns_port">本地DNS端口</string>
<string name="title_pref_feedback">回饋</string> <string name="title_pref_feedback">回饋</string>
<string name="summary_pref_feedback">回饋提升或前往 GitHub 回報 Bug</string> <string name="summary_pref_feedback">回饋提升或前往 GitHub 回報 Bug</string>

View File

@@ -5,6 +5,7 @@
<item>aes-128-gcm</item> <item>aes-128-gcm</item>
<item>auto</item> <item>auto</item>
<item>none</item> <item>none</item>
<item>zero</item>
</string-array> </string-array>
<string-array name="ss_securitys" translatable="false"> <string-array name="ss_securitys" translatable="false">
<item>aes-256-cfb</item> <item>aes-256-cfb</item>
@@ -23,11 +24,16 @@
<item>ws</item> <item>ws</item>
<item>h2</item> <item>h2</item>
<item>quic</item> <item>quic</item>
<item>grpc</item>
</string-array> </string-array>
<string-array name="headertypes" translatable="false"> <string-array name="header_type_tcp" translatable="false">
<item>none</item> <item>none</item>
<item>http</item> <item>http</item>
</string-array>
<string-array name="header_type_kcp_and_quic" translatable="false">
<item>none</item>
<item>srtp</item> <item>srtp</item>
<item>utp</item> <item>utp</item>
<item>wechat-video</item> <item>wechat-video</item>
@@ -35,24 +41,27 @@
<item>wireguard</item> <item>wireguard</item>
</string-array> </string-array>
<string-array name="headertypetcps" translatable="false"> <string-array name="mode_type_grpc" translatable="false">
<item>none</item> <item>gun</item>
<item>http</item> <item>multi</item>
</string-array> <!--Hide this option until core support it <item>guna</item>-->
<string-array name="headertypekcps" translatable="false">
<item>none</item>
<item>srtp</item>
<item>utp</item>
<item>wechat-video</item>
<item>dtls</item>
<item>wireguard</item>
</string-array> </string-array>
<string-array name="streamsecuritys" translatable="false"> <string-array name="streamsecuritys" translatable="false">
<item></item> <item></item>
<item>tls</item> <item>tls</item>
</string-array> </string-array>
<string-array name="streamsecurityxs" translatable="false">
<item></item>
<item>tls</item>
<item>xtls</item>
</string-array>
<string-array name="allowinsecures" translatable="false">
<item></item>
<item>true</item>
<item>false</item>
</string-array>
<string-array name="routing_mode_value" translatable="false"> <string-array name="routing_mode_value" translatable="false">
<item>0</item> <item>0</item>
@@ -67,11 +76,30 @@
<item>IPOnDemand</item> <item>IPOnDemand</item>
</string-array> </string-array>
<string-array name="core_loglevel" translatable="false">
<item>debug</item>
<item>info</item>
<item>warning</item>
<item>error</item>
<item>none</item>
</string-array>
<string-array name="mode_value" translatable="false"> <string-array name="mode_value" translatable="false">
<item>VPN</item> <item>VPN</item>
<item>Proxy only</item> <item>Proxy only</item>
</string-array> </string-array>
<string-array name="flows" translatable="false">
<item></item>
<item>xtls-rprx-origin</item>
<item>xtls-rprx-origin-udp443</item>
<item>xtls-rprx-direct</item>
<item>xtls-rprx-direct-udp443</item>
<item>xtls-rprx-splice</item>
<item>xtls-rprx-splice-udp443</item>
</string-array>
<!-- minimum list https://serverfault.com/a/304791 --> <!-- minimum list https://serverfault.com/a/304791 -->
<string-array name="bypass_private_ip_address" translatable="false"> <string-array name="bypass_private_ip_address" translatable="false">
<item>0.0.0.0/5</item> <item>0.0.0.0/5</item>

View File

@@ -6,6 +6,8 @@
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string> <string name="app_tile_first_use">First use of this feature, please use the app to add server</string>
<string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">Data migration success!</string>
<string name="migration_fail">Data migration failed!</string>
<!-- Notifications --> <!-- Notifications -->
<string name="notification_action_stop_v2ray">Stop</string> <string name="notification_action_stop_v2ray">Stop</string>
@@ -38,11 +40,12 @@
<string name="server_lab_id">id</string> <string name="server_lab_id">id</string>
<string name="server_lab_alterid">alterId</string> <string name="server_lab_alterid">alterId</string>
<string name="server_lab_security">security</string> <string name="server_lab_security">security</string>
<string name="server_lab_network">network</string> <string name="server_lab_network">Network</string>
<string name="server_lab_more_function">more function</string> <string name="server_lab_more_function">more function</string>
<string name="server_lab_head_type">head type</string> <string name="server_lab_head_type">head type</string>
<string name="server_lab_mode_type">gRPC mode</string>
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string> <string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC key</string> <string name="server_lab_path">path(ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
<string name="server_lab_stream_security">tls</string> <string name="server_lab_stream_security">tls</string>
<string name="server_lab_allow_insecure">allowInsecure</string> <string name="server_lab_allow_insecure">allowInsecure</string>
<string name="server_lab_address3">address</string> <string name="server_lab_address3">address</string>
@@ -75,7 +78,7 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">Settings</string> <string name="title_settings">Settings</string>
<string name="title_advanced">Advanced Settings</string> <string name="title_advanced">Advanced Settings</string>
<string name="title_vpn_settings">VPN Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string> <string name="title_pref_per_app_proxy">Per-app proxy</string>
<string name="summary_pref_per_app_proxy">Per-app proxy mode only support Android 5.0 Lollipop or higher</string> <string name="summary_pref_per_app_proxy">Per-app proxy mode only support Android 5.0 Lollipop or higher</string>
@@ -87,10 +90,14 @@
usage.</string> usage.</string>
<string name="title_pref_sniffing_enabled">Enable Sniffing</string> <string name="title_pref_sniffing_enabled">Enable Sniffing</string>
<string name="summary_pref_sniffing_enabled">Sniffing</string> <string name="summary_pref_sniffing_enabled">Try sniff domain from the packet (default on)</string>
<string name="title_pref_local_dns_enabled">Enable local DNS</string> <string name="title_pref_local_dns_enabled">Enable local DNS</string>
<string name="summary_pref_local_dns_enabled">DNS processed by v2ray-core (BETA)</string> <string name="summary_pref_local_dns_enabled">DNS processed by cores DNS module (Recommended, if need routing Bypassing LAN and
mainland address)</string>
<string name="title_pref_fake_dns_enabled">Enable fake DNS</string>
<string name="summary_pref_fake_dns_enabled">local DNS returns fake IP address (faster, but it may not work for some apps)</string>
<string name="title_pref_forward_ipv6">IPv6 Route</string> <string name="title_pref_forward_ipv6">IPv6 Route</string>
<string name="summary_pref_forward_ipv6">Redirect IPv6 traffic to remote (BETA)</string> <string name="summary_pref_forward_ipv6">Redirect IPv6 traffic to remote (BETA)</string>
@@ -107,7 +114,9 @@
<string name="title_pref_remote_dns">Remote DNS (Optional)</string> <string name="title_pref_remote_dns">Remote DNS (Optional)</string>
<string name="summary_pref_remote_dns">DNS</string> <string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_domestic_dns">Domestic DNS (Optional, effects on LocalDNS mode)</string> <string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
<string name="summary_pref_domestic_dns">DNS</string> <string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5 proxy port</string> <string name="title_pref_socks_port">SOCKS5 proxy port</string>
@@ -116,6 +125,9 @@
<string name="title_pref_http_port">HTTP proxy port</string> <string name="title_pref_http_port">HTTP proxy port</string>
<string name="summary_pref_http_port">HTTP proxy port</string> <string name="summary_pref_http_port">HTTP proxy port</string>
<string name="title_pref_local_dns_port">Local DNS port</string>
<string name="summary_pref_local_dns_port">Local DNS port</string>
<string name="title_pref_feedback">Feedback</string> <string name="title_pref_feedback">Feedback</string>
<string name="summary_pref_feedback">Feedback enhancements or bugs to GitHub</string> <string name="summary_pref_feedback">Feedback enhancements or bugs to GitHub</string>
<string name="summary_pref_tg_group">Join Telegram Group</string> <string name="summary_pref_tg_group">Join Telegram Group</string>

View File

@@ -1,6 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/title_settings"> <CheckBoxPreference
android:key="pref_speed_enabled"
android:summary="@string/summary_pref_speed_enabled"
android:title="@string/title_pref_speed_enabled" />
<CheckBoxPreference
android:defaultValue="true"
android:key="pref_sniffing_enabled"
android:summary="@string/summary_pref_sniffing_enabled"
android:title="@string/title_pref_sniffing_enabled" />
<PreferenceCategory android:title="@string/title_vpn_settings">
<CheckBoxPreference <CheckBoxPreference
android:key="pref_per_app_proxy" android:key="pref_per_app_proxy"
android:summary="@string/summary_pref_per_app_proxy" android:summary="@string/summary_pref_per_app_proxy"
@@ -10,24 +21,26 @@
<!--android:key="pref_mux_enabled"--> <!--android:key="pref_mux_enabled"-->
<!--android:summary="@string/summary_pref_mux_enabled"--> <!--android:summary="@string/summary_pref_mux_enabled"-->
<!--android:title="@string/title_pref_mux_enabled" />--> <!--android:title="@string/title_pref_mux_enabled" />-->
<CheckBoxPreference
android:key="pref_local_dns_enabled"
android:summary="@string/summary_pref_local_dns_enabled"
android:title="@string/title_pref_local_dns_enabled" />
<CheckBoxPreference <CheckBoxPreference
android:key="pref_speed_enabled" android:key="pref_fake_dns_enabled"
android:summary="@string/summary_pref_speed_enabled" android:summary="@string/summary_pref_fake_dns_enabled"
android:title="@string/title_pref_speed_enabled" /> android:title="@string/title_pref_fake_dns_enabled" />
<CheckBoxPreference <EditTextPreference
android:defaultValue="true" android:key="pref_local_dns_port"
android:key="pref_sniffing_enabled" android:summary="10807"
android:summary="@string/summary_pref_sniffing_enabled" android:inputType="number"
android:title="@string/title_pref_sniffing_enabled" /> android:title="@string/title_pref_local_dns_port" />
<CheckBoxPreference <EditTextPreference
android:defaultValue="false" android:key="pref_vpn_dns"
android:key="pref_proxy_sharing_enabled" android:summary="@string/summary_pref_remote_dns"
android:onClick="proxySharingOnClick" android:title="@string/title_pref_vpn_dns" />
android:summary="@string/summary_pref_proxy_sharing_enabled"
android:title="@string/title_pref_proxy_sharing_enabled" />
</PreferenceCategory> </PreferenceCategory>
@@ -40,6 +53,11 @@
android:summary="%s" android:summary="%s"
android:title="@string/title_pref_routing_domain_strategy" /> android:title="@string/title_pref_routing_domain_strategy" />
<Preference
android:key="pref_routing_custom"
android:summary="@string/title_pref_routing_custom"
android:title="@string/title_pref_routing_custom" />
<ListPreference <ListPreference
android:defaultValue="0" android:defaultValue="0"
android:entries="@array/routing_mode" android:entries="@array/routing_mode"
@@ -47,11 +65,6 @@
android:key="pref_routing_mode" android:key="pref_routing_mode"
android:summary="%s" android:summary="%s"
android:title="@string/title_pref_routing_mode" /> android:title="@string/title_pref_routing_mode" />
<Preference
android:key="pref_routing_custom"
android:summary="@string/title_pref_routing_custom"
android:title="@string/title_pref_routing_custom" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/title_advanced"> <PreferenceCategory android:title="@string/title_advanced">
@@ -62,19 +75,11 @@
android:title="@string/title_pref_forward_ipv6" /> android:title="@string/title_pref_forward_ipv6" />
<CheckBoxPreference <CheckBoxPreference
android:key="pref_local_dns_enabled" android:defaultValue="false"
android:summary="@string/summary_pref_local_dns_enabled" android:key="pref_proxy_sharing_enabled"
android:title="@string/title_pref_local_dns_enabled" /> android:onClick="proxySharingOnClick"
android:summary="@string/summary_pref_proxy_sharing_enabled"
<EditTextPreference android:title="@string/title_pref_proxy_sharing_enabled" />
android:key="pref_domestic_dns"
android:summary="@string/summary_pref_domestic_dns"
android:title="@string/title_pref_domestic_dns" />
<EditTextPreference
android:key="pref_remote_dns"
android:summary="@string/summary_pref_remote_dns"
android:title="@string/title_pref_remote_dns" />
<Preference <Preference
android:key="pref_socks_port" android:key="pref_socks_port"
@@ -86,6 +91,16 @@
android:summary="10809" android:summary="10809"
android:title="@string/title_pref_http_port" /> android:title="@string/title_pref_http_port" />
<EditTextPreference
android:key="pref_remote_dns"
android:summary="@string/summary_pref_remote_dns"
android:title="@string/title_pref_remote_dns" />
<EditTextPreference
android:key="pref_domestic_dns"
android:summary="@string/summary_pref_domestic_dns"
android:title="@string/title_pref_domestic_dns" />
<ListPreference <ListPreference
android:defaultValue="VPN" android:defaultValue="VPN"
android:entries="@array/mode_entries" android:entries="@array/mode_entries"

View File

@@ -1 +0,0 @@
/build

View File

@@ -1,25 +0,0 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion buildToolsVer
defaultConfig {
minSdkVersion 17
targetSdkVersion Integer.parseInt("$targetSdkVer")
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.13'
implementation "com.android.support:support-annotations:$supportLibVersion"
implementation 'com.google.code.gson:gson:2.8.6'
}

View File

@@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/wangyida/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# 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 *;
#}

View File

@@ -1,16 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.dozen.dpreference">
<application
android:allowBackup="true"
android:supportsRtl="true">
<provider
android:name="me.dozen.dpreference.PreferenceProvider"
android:enabled="true"
android:authorities="com.v2ray.ang.dpreference"
android:exported="false" />
</application>
</manifest>

View File

@@ -1,86 +0,0 @@
package me.dozen.dpreference;
import android.content.Context;
import android.text.TextUtils;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Created by wangyida on 15-4-9.
*/
public class DPreference {
Context mContext;
/**
* preference file name
*/
String mName;
private DPreference() {
}
public DPreference(Context context, String name) {
this.mContext = context;
this.mName = name;
}
public String getPrefString(final String key, final String defaultValue) {
return PrefAccessor.getString(mContext, mName, key, defaultValue);
}
public void setPrefString(final String key, final String value) {
PrefAccessor.setString(mContext, mName, key, value);
}
public boolean getPrefBoolean(final String key, final boolean defaultValue) {
return PrefAccessor.getBoolean(mContext, mName, key, defaultValue);
}
public void setPrefBoolean(final String key, final boolean value) {
PrefAccessor.setBoolean(mContext, mName, key, value);
}
public void setPrefInt(final String key, final int value) {
PrefAccessor.setInt(mContext, mName, key, value);
}
public int getPrefInt(final String key, final int defaultValue) {
return PrefAccessor.getInt(mContext, mName, key, defaultValue);
}
public void setPrefLong(final String key, final long value) {
PrefAccessor.setLong(mContext, mName, key, value);
}
public long getPrefLong(final String key, final long defaultValue) {
return PrefAccessor.getLong(mContext, mName, key, defaultValue);
}
public void setPrefStringSet(final String key, final Set<String> value) {
PrefAccessor.setStringSet(mContext, mName, key, value);
}
public Set<String> getPrefStringSet(final String key, final Set<String> defaultValue) {
return PrefAccessor.getStringSet(mContext, mName, key, defaultValue);
}
public void setPrefStringOrderedSet(final String key, final LinkedHashSet<String> value) {
PrefAccessor.setString(mContext, mName, key, StringSetConverter.encode(value));
}
public LinkedHashSet<String> getPrefStringOrderedSet(final String key, final LinkedHashSet<String> defaultValue) {
String value = PrefAccessor.getString(mContext, mName, key, "");
if (TextUtils.isEmpty(value)) {
return defaultValue;
}
return StringSetConverter.decode(value);
}
public void removePreference(final String key) {
PrefAccessor.remove(mContext, mName, key);
}
}

View File

@@ -1,61 +0,0 @@
package me.dozen.dpreference;
import android.database.Cursor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
final class IOUtils {
// NOTE: This class is focussed on InputStream, OutputStream, Reader and
// Writer. Each method should take at least one of these as a parameter,
// or return one of them.
private static final int EOF = -1;
/**
* The default buffer size ({@value}) to use for {@link
*/
private static final int DEFAULT_BUFFER_SIZE = 1024;
public static void closeQuietly(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
// ignore
}
}
}
public static void closeQuietly(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
// ignore
}
}
}
public static void closeQuietly(Reader r) {
if (r != null) {
try {
r.close();
} catch (IOException e) {
// ignore
}
}
}
public static void closeQuietly(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
}

View File

@@ -1,39 +0,0 @@
package me.dozen.dpreference;
import java.util.Set;
/**
* Created by wangyida on 15/12/18.
*/
interface IPrefImpl {
String getPrefString(String key, String defaultValue);
void setPrefString(String key, String value);
boolean getPrefBoolean(String key, boolean defaultValue);
void setPrefBoolean(final String key, final boolean value);
void setPrefInt(final String key, final int value);
int getPrefInt(final String key, final int defaultValue);
void setPrefFloat(final String key, final float value);
float getPrefFloat(final String key, final float defaultValue);
void setPrefLong(final String key, final long value);
long getPrefLong(final String key, final long defaultValue);
Set<String> getPrefStringSet(String key, Set<String> defaultValue);
void setPrefStringSet(String key, Set<String> value);
void removePreference(final String key);
boolean hasKey(String key);
}

View File

@@ -1,116 +0,0 @@
package me.dozen.dpreference;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import java.util.Set;
/**
* Created by wangyida on 15/12/18.
*/
class PrefAccessor {
public static String getString(Context context, String name, String key, String defaultValue) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
String value = defaultValue;
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
value = cursor.getString(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
}
IOUtils.closeQuietly(cursor);
return value;
}
public static int getInt(Context context, String name, String key, int defaultValue) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_INT);
int value = defaultValue;
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
value = cursor.getInt(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
}
IOUtils.closeQuietly(cursor);
return value;
}
public static long getLong(Context context, String name, String key, long defaultValue) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_LONG);
long value = defaultValue;
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
value = cursor.getLong(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
}
IOUtils.closeQuietly(cursor);
return value;
}
public static boolean getBoolean(Context context, String name, String key, boolean defaultValue) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_BOOLEAN);
int value = defaultValue ? 1 : 0;
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
value = cursor.getInt(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
}
IOUtils.closeQuietly(cursor);
return value == 1;
}
public static Set<String> getStringSet(Context context, String name, String key, Set<String> defaultValue) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING_SET);
Set<String> value = defaultValue;
Cursor cursor = context.getContentResolver().query(URI, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String cursorString = cursor.getString(cursor.getColumnIndex(PreferenceProvider.PREF_VALUE));
value = StringSetConverter.decode(cursorString);
}
IOUtils.closeQuietly(cursor);
return value;
}
public static void remove(Context context, String name, String key) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
context.getContentResolver().delete(URI, null, null);
}
public static void setString(Context context, String name, String key, String value) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING);
ContentValues cv = new ContentValues();
cv.put(PreferenceProvider.PREF_KEY, key);
cv.put(PreferenceProvider.PREF_VALUE, value);
context.getContentResolver().update(URI, cv, null, null);
}
public static void setBoolean(Context context, String name, String key, boolean value) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_BOOLEAN);
ContentValues cv = new ContentValues();
cv.put(PreferenceProvider.PREF_KEY, key);
cv.put(PreferenceProvider.PREF_VALUE, value);
context.getContentResolver().update(URI, cv, null, null);
}
public static void setInt(Context context, String name, String key, int value) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_INT);
ContentValues cv = new ContentValues();
cv.put(PreferenceProvider.PREF_KEY, key);
cv.put(PreferenceProvider.PREF_VALUE, value);
context.getContentResolver().update(URI, cv, null, null);
}
public static void setLong(Context context, String name, String key, long value) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_LONG);
ContentValues cv = new ContentValues();
cv.put(PreferenceProvider.PREF_KEY, key);
cv.put(PreferenceProvider.PREF_VALUE, value);
context.getContentResolver().update(URI, cv, null, null);
}
public static void setStringSet(Context context, String name, String key, Set<String> value) {
Uri URI = PreferenceProvider.buildUri(name, key, PreferenceProvider.PREF_STRING_SET);
ContentValues cv = new ContentValues();
cv.put(PreferenceProvider.PREF_KEY, key);
cv.put(PreferenceProvider.PREF_VALUE, StringSetConverter.encode(value));
context.getContentResolver().update(URI, cv, null, null);
}
}

View File

@@ -1,133 +0,0 @@
package me.dozen.dpreference;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.Set;
/**
* Created by wangyida on 15/12/18.
*/
class PreferenceImpl implements IPrefImpl {
private Context mContext;
private String mPrefName;
public PreferenceImpl(Context context, String prefName) {
mContext = context;
mPrefName = prefName;
}
public String getPrefString(final String key,
final String defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getString(key, defaultValue);
}
public void setPrefString(final String key, final String value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putString(key, value).apply();
}
public boolean getPrefBoolean(final String key,
final boolean defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getBoolean(key, defaultValue);
}
public boolean hasKey(final String key) {
return mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE)
.contains(key);
}
public void setPrefBoolean(final String key, final boolean value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putBoolean(key, value).apply();
}
public void setPrefInt(final String key, final int value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putInt(key, value).apply();
}
@Override
public Set<String> getPrefStringSet(String key, Set<String> defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getStringSet(key, defaultValue);
}
@Override
public void setPrefStringSet(String key, Set<String> value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putStringSet(key, value).apply();
}
public void increasePrefInt(final String key) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
increasePrefInt(settings, key);
}
public void increasePrefInt(final SharedPreferences sp, final String key) {
final int v = sp.getInt(key, 0) + 1;
sp.edit().putInt(key, v).apply();
}
public void increasePrefInt(final SharedPreferences sp, final String key,
final int increment) {
final int v = sp.getInt(key, 0) + increment;
sp.edit().putInt(key, v).apply();
}
public int getPrefInt(final String key, final int defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getInt(key, defaultValue);
}
public void setPrefFloat(final String key, final float value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putFloat(key, value).apply();
}
public float getPrefFloat(final String key, final float defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getFloat(key, defaultValue);
}
public void setPrefLong(final String key, final long value) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
settings.edit().putLong(key, value).apply();
}
public long getPrefLong(final String key, final long defaultValue) {
final SharedPreferences settings =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
return settings.getLong(key, defaultValue);
}
public void removePreference(final String key) {
final SharedPreferences prefs =
mContext.getSharedPreferences(mPrefName, Context.MODE_PRIVATE);
prefs.edit().remove(key).apply();
}
public void clearPreference(final SharedPreferences p) {
final SharedPreferences.Editor editor = p.edit();
editor.clear();
editor.apply();
}
}

View File

@@ -1,267 +0,0 @@
package me.dozen.dpreference;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.util.HashMap;
import java.util.Map;
/**
* Created by wangyida on 15/12/18.
*/
public class PreferenceProvider extends ContentProvider {
private static final String TAG = PreferenceProvider.class.getSimpleName();
private static final String AUTHORITY = "com.v2ray.ang.dpreference";
public static final String CONTENT_PREF_BOOLEAN_URI = "content://" + AUTHORITY + "/boolean/";
public static final String CONTENT_PREF_STRING_URI = "content://" + AUTHORITY + "/string/";
public static final String CONTENT_PREF_INT_URI = "content://" + AUTHORITY + "/integer/";
public static final String CONTENT_PREF_LONG_URI = "content://" + AUTHORITY + "/long/";
public static final String CONTENT_PREF_STRING_SET_URI = "content://" + AUTHORITY + "/string_set/";
public static final String PREF_KEY = "key";
public static final String PREF_VALUE = "value";
public static final int PREF_BOOLEAN = 1;
public static final int PREF_STRING = 2;
public static final int PREF_INT = 3;
public static final int PREF_LONG = 4;
public static final int PREF_STRING_SET = 5;
private static final UriMatcher sUriMatcher;
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(AUTHORITY, "boolean/*/*", PREF_BOOLEAN);
sUriMatcher.addURI(AUTHORITY, "string/*/*", PREF_STRING);
sUriMatcher.addURI(AUTHORITY, "integer/*/*", PREF_INT);
sUriMatcher.addURI(AUTHORITY, "long/*/*", PREF_LONG);
sUriMatcher.addURI(AUTHORITY, "string_set/*/*", PREF_STRING_SET);
}
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor cursor = null;
PrefModel model = getPrefModelByUri(uri);
switch (sUriMatcher.match(uri)) {
case PREF_BOOLEAN:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefBoolean(model.getKey(), false) ? 1 : 0);
}
break;
case PREF_STRING:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefString(model.getKey(), ""));
}
break;
case PREF_INT:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefInt(model.getKey(), -1));
}
break;
case PREF_LONG:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefLong(model.getKey(), -1));
}
break;
case PREF_STRING_SET:
if (getDPreference(model.getName()).hasKey(model.getKey())) {
cursor = preferenceToCursor(getDPreference(model.getName()).getPrefStringSet(model.getKey(), null));
}
break;
}
return cursor;
}
@Nullable
@Override
public String getType(Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new IllegalStateException("insert unsupport!!!");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
switch (sUriMatcher.match(uri)) {
case PREF_BOOLEAN:
case PREF_LONG:
case PREF_STRING:
case PREF_INT:
case PREF_STRING_SET:
PrefModel model = getPrefModelByUri(uri);
if (model != null) {
getDPreference(model.getName()).removePreference(model.getKey());
}
break;
default:
throw new IllegalStateException(" unsupported uri : " + uri);
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
PrefModel model = getPrefModelByUri(uri);
if (model == null) {
throw new IllegalArgumentException("update prefModel is null");
}
switch (sUriMatcher.match(uri)) {
case PREF_BOOLEAN:
persistBoolean(model.getName(), values);
break;
case PREF_LONG:
persistLong(model.getName(), values);
break;
case PREF_STRING:
persistString(model.getName(), values);
break;
case PREF_INT:
persistInt(model.getName(), values);
break;
case PREF_STRING_SET:
persistStringSet(model.getName(), values);
break;
default:
throw new IllegalStateException("update unsupported uri : " + uri);
}
return 0;
}
private static String[] PREFERENCE_COLUMNS = {PREF_VALUE};
private <T> MatrixCursor preferenceToCursor(T value) {
MatrixCursor matrixCursor = new MatrixCursor(PREFERENCE_COLUMNS, 1);
MatrixCursor.RowBuilder builder = matrixCursor.newRow();
builder.add(value);
return matrixCursor;
}
private void persistInt(String name, ContentValues values) {
if (values == null) {
throw new IllegalArgumentException(" values is null!!!");
}
String kInteger = values.getAsString(PREF_KEY);
int vInteger = values.getAsInteger(PREF_VALUE);
getDPreference(name).setPrefInt(kInteger, vInteger);
}
private void persistBoolean(String name, ContentValues values) {
if (values == null) {
throw new IllegalArgumentException(" values is null!!!");
}
String kBoolean = values.getAsString(PREF_KEY);
boolean vBoolean = values.getAsBoolean(PREF_VALUE);
getDPreference(name).setPrefBoolean(kBoolean, vBoolean);
}
private void persistLong(String name, ContentValues values) {
if (values == null) {
throw new IllegalArgumentException(" values is null!!!");
}
String kLong = values.getAsString(PREF_KEY);
long vLong = values.getAsLong(PREF_VALUE);
getDPreference(name).setPrefLong(kLong, vLong);
}
private void persistString(String name, ContentValues values) {
if (values == null) {
throw new IllegalArgumentException(" values is null!!!");
}
String kString = values.getAsString(PREF_KEY);
String vString = values.getAsString(PREF_VALUE);
getDPreference(name).setPrefString(kString, vString);
}
private void persistStringSet(String name, ContentValues values) {
if (values == null) {
throw new IllegalArgumentException(" values is null!!!");
}
String kString = values.getAsString(PREF_KEY);
String vString = values.getAsString(PREF_VALUE);
getDPreference(name).setPrefStringSet(kString, StringSetConverter.decode(vString));
}
private static Map<String, IPrefImpl> sPreferences = new HashMap<>();
private IPrefImpl getDPreference(String name) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("getDPreference name is null!!!");
}
if (sPreferences.get(name) == null) {
IPrefImpl pref = new PreferenceImpl(getContext(), name);
sPreferences.put(name, pref);
}
return sPreferences.get(name);
}
private PrefModel getPrefModelByUri(Uri uri) {
if (uri == null || uri.getPathSegments().size() != 3) {
throw new IllegalArgumentException("getPrefModelByUri uri is wrong : " + uri);
}
String name = uri.getPathSegments().get(1);
String key = uri.getPathSegments().get(2);
return new PrefModel(name, key);
}
public static Uri buildUri(String name, String key, int type) {
return Uri.parse(getUriByType(type) + name + "/" + key);
}
private static String getUriByType(int type) {
switch (type) {
case PreferenceProvider.PREF_BOOLEAN:
return PreferenceProvider.CONTENT_PREF_BOOLEAN_URI;
case PreferenceProvider.PREF_INT:
return PreferenceProvider.CONTENT_PREF_INT_URI;
case PreferenceProvider.PREF_LONG:
return PreferenceProvider.CONTENT_PREF_LONG_URI;
case PreferenceProvider.PREF_STRING:
return PreferenceProvider.CONTENT_PREF_STRING_URI;
case PreferenceProvider.PREF_STRING_SET:
return PreferenceProvider.CONTENT_PREF_STRING_SET_URI;
}
throw new IllegalStateException("unsupport preftype : " + type);
}
private static class PrefModel {
String name;
String key;
public PrefModel(String name, String key) {
this.name = name;
this.key = key;
}
public String getName() {
return name;
}
public String getKey() {
return key;
}
}
}

View File

@@ -1,22 +0,0 @@
package me.dozen.dpreference;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set;
public class StringSetConverter {
private static final Gson gson = new Gson();
public static String encode(Set<String> src) {
return gson.toJson(src);
}
public static LinkedHashSet<String> decode(String json) {
Type setType = new TypeToken<LinkedHashSet<String>>() {
}.getType();
return gson.fromJson(json, setType);
}
}

View File

@@ -1 +1 @@
include ':app', ':dpreference' include ':app'