Compare commits

..

24 Commits

Author SHA1 Message Date
2dust f25aaf43d7 Update V2RayVpnService.kt 2019-09-24 13:12:39 +08:00
2dust 3b365533fc Create libv2ray.aar 2019-09-02 08:48:17 +08:00
2dust a982327030 Update .gitignore 2019-09-02 08:48:11 +08:00
2dust 6ce12b2ea5 Update AndroidManifest.xml 2019-09-02 08:47:01 +08:00
2dust d7f3ed5182 Update BaseDrawerActivity.kt 2019-09-02 08:46:58 +08:00
2dust f519103b3e Update MainActivity.kt 2019-09-02 08:46:54 +08:00
2dust d7c40ee883 Update SettingsActivity.kt 2019-09-02 08:46:50 +08:00
2dust 809d50689b Update gradle.properties 2019-09-02 08:46:36 +08:00
2dust 042def7e83 Update V2RayVpnService.kt 2019-08-27 15:14:22 +08:00
2dust 8bb0d6b175 Create Readme.md 2019-08-22 11:26:07 +08:00
2dust 3ae76fab72 add lib 2019-08-18 18:15:37 +08:00
2dust 46a63e8704 Create readme.txt 2019-08-18 18:14:52 +08:00
2dust 12f8b1584c Create issue_template.md 2019-08-14 07:55:48 +08:00
2dust 2b571e2a94 Merge pull request #44 from cutelua/dev
improved notification
2019-07-30 14:47:38 +08:00
cutelua ba30697309 no need for USER_PRESENT 2019-07-11 09:19:30 +08:00
2dust 879e25bb82 sdk28 2019-07-10 14:24:09 +08:00
cutelua 17c858d24c only show speed if core isrunning 2019-07-09 15:58:36 +08:00
cutelua 71a6531835 stop speed query when screen off; resume when screen on 2019-07-09 14:32:47 +08:00
cutelua 5d760382d7 pass callback as V2RayPoint arg; stop on LowMemory 2019-07-09 13:06:28 +08:00
cutelua 4dda36673a allow go to shutdown VpnService 2019-07-09 11:43:31 +08:00
cutelua 5aca55cb8f only use title to show speed 2019-07-07 16:42:22 +08:00
cutelua 82f14f0d07 show total speed as notification title 2019-07-07 16:42:22 +08:00
2dust c0e968ac34 up ver 2019-07-01 13:22:48 +08:00
2dust 29d3fb710b ii 2019-05-15 10:17:59 +08:00
121 changed files with 4511 additions and 4302 deletions
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: V2Ray程序问题
url: https://github.com/v2fly/v2ray-core/
about: 如果您有V2Ray而非v2rayNG的问题,请至这个链接讨论。
@@ -1,8 +1,3 @@
---
name: v2rayNG程序问题
about: 创建一个报告来帮助我们改进
---
在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。
@@ -19,8 +14,7 @@ about: 创建一个报告来帮助我们改进
### 日志信息
<details>
通过`adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main`获取日志。请自行删减日志中可能出现的敏感信息。
通过 `adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main` 获取日志。请自行删减日志中可能出现的敏感信息。
如果问题可重现,建议先执行`adb logcat -c`清空系统日志再执行上述命令,再操作重现问题。
```
+5 -3
View File
@@ -2,8 +2,10 @@ V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml
V2rayNG/app/src/main/assets/geoip.dat
V2rayNG/app/src/main/assets/geosite.dat
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
*.dat
*.jks
V2rayNG/app/release/output.json
.idea/
.gradle/
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/app/release/output.json
-2
View File
@@ -1,2 +0,0 @@
*.jar
*.aar
+26
View File
@@ -0,0 +1,26 @@
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
+5 -7
View File
@@ -1,20 +1,18 @@
package CoreI
import (
v2core "github.com/xtls/xray-core/core"
v2core "v2ray.com/core"
)
type Status struct {
IsRunning bool
IsTRunning bool
PackageName string
PackageCodePath string
IsRunning bool
PackageName string
Vpoint v2core.Server
}
func CheckVersion() int {
return 23
return 20
}
func (v *Status) GetDataDir() string {
@@ -22,7 +20,7 @@ func (v *Status) GetDataDir() string {
}
func (v *Status) GetApp(name string) string {
return v.PackageCodePath + name
return v.PackageName + name
}
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
+11 -3
View File
@@ -8,19 +8,27 @@ 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/geoip.dat > geoip.dat
shippedBinary:
cd shippedBinarys; $(MAKE) shippedBinary
fetchDep:
-go get github.com/2dust/AndroidLibV2rayLite
go get github.com/2dust/AndroidLibV2rayLite
ANDROID_HOME=$(HOME)/android-sdk-linux
export ANDROID_HOME
PATH:=$(PATH):$(GOPATH)/bin
export PATH
downloadGoMobile:
go get golang.org/x/mobile/cmd/...
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
ls ~
ls ~/android-sdk-linux/
gomobile init ;gomobile bind -v -tags json github.com/2dust/AndroidLibV2rayLite
BuildMobile:
gomobile init
gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
@echo Stub
all: asset pb
all: asset pb shippedBinary fetchDep
@echo DONE
+9 -1
View File
@@ -3,13 +3,14 @@ package Escort
import (
"os"
"os/exec"
"time"
"log"
"github.com/2dust/AndroidLibV2rayLite/CoreI"
)
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string) {
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, sendFd func() int) {
log.Println(proc, pt)
count := 0
for count <= 42 {
@@ -36,6 +37,13 @@ func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string) {
*v.escortProcess = append(*v.escortProcess, cmd.Process)
log.Println("EscortRun Waiting....")
if count > 0 {
go func() {
time.Sleep(time.Second)
sendFd()
}()
}
if err := cmd.Wait(); err != nil {
log.Println("EscortRun cmd.Wait err:", err)
}
@@ -12,10 +12,8 @@ import (
"time"
"golang.org/x/sys/unix"
v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/outbound"
v2internet "github.com/xtls/xray-core/transport/internet"
v2net "v2ray.com/core/common/net"
v2internet "v2ray.com/core/transport/internet"
)
type protectSet interface {
@@ -192,11 +190,6 @@ func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
return
}
// Init implement internet.SystemDialer
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
// do nothing
}
// Dial exported as the protected dial method
func (d *ProtectedDialer) Dial(ctx context.Context,
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {
@@ -9,7 +9,7 @@ import (
"testing"
"time"
v2net "github.com/xtls/xray-core/common/net"
v2net "v2ray.com/core/common/net"
)
type fakeSupportSet struct{}
+11 -7
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/
pushd $TMPDIR
git clone --depth=1 https://github.com/XTLS/badvpn.git
git clone --depth=1 https://github.com/XTLS/libancillary.git
git clone --depth=1 https://github.com/shadowsocks/badvpn.git
git clone --depth=1 https://github.com/shadowsocks/libancillary.git
$NDK_HOME/ndk-build \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=./tun2socks.mk \
@@ -34,10 +34,14 @@ $NDK_HOME/ndk-build \
NDK_OUT=$TMPDIR/tmp \
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
install -v -m755 libs/arm64-v8a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
install -v -m755 libs/x86/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
install -v -m755 libs/x86_64/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/shippedBinarys/ArchDep/arm/
install -v -m755 libs/arm64-v8a/tun2socks $__dir/shippedBinarys/ArchDep/arm64/
install -v -m755 libs/x86/tun2socks $__dir/shippedBinarys/ArchDep/386/
install -v -m755 libs/x86_64/tun2socks $__dir/shippedBinarys/ArchDep/amd64/
popd
rm -rf $TMPDIR
pushd $__dir/shippedBinarys
make clean && make shippedBinary
popd
rm -rf $TMPDIR
-9
View File
@@ -1,9 +0,0 @@
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
View File
@@ -1,303 +0,0 @@
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=
+36 -41
View File
@@ -11,21 +11,22 @@ import (
"github.com/2dust/AndroidLibV2rayLite/CoreI"
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
"github.com/2dust/AndroidLibV2rayLite/VPN"
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
mobasset "golang.org/x/mobile/asset"
v2core "github.com/xtls/xray-core/core"
v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
v2stats "github.com/xtls/xray-core/features/stats"
v2serial "github.com/xtls/xray-core/infra/conf/serial"
_ "github.com/xtls/xray-core/main/distro/all"
v2internet "github.com/xtls/xray-core/transport/internet"
v2core "v2ray.com/core"
v2filesystem "v2ray.com/core/common/platform/filesystem"
v2stats "v2ray.com/core/features/stats"
v2serial "v2ray.com/core/infra/conf/serial"
_ "v2ray.com/core/main/distro/all"
v2internet "v2ray.com/core/transport/internet"
v2applog "github.com/xtls/xray-core/app/log"
v2commlog "github.com/xtls/xray-core/common/log"
v2applog "v2ray.com/core/app/log"
v2commlog "v2ray.com/core/common/log"
)
const (
v2Assert = "xray.location.asset"
v2Assert = "v2ray.location.asset"
assetperfix = "/dev/libv2rayfs0/asset"
)
@@ -43,12 +44,10 @@ type V2RayPoint struct {
closeChan chan struct{}
PackageName string
PackageCodePath string
DomainName string
ConfigureFileContent string
EnableLocalDNS bool
ForwardIpv6 bool
ProxyOnly bool
}
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
@@ -58,6 +57,7 @@ type V2RayVPNServiceSupportsSet interface {
Shutdown() int
Protect(int) int
OnEmitStatus(int, string) int
SendFd() int
}
/*RunLoop Run V2Ray main loop
@@ -67,7 +67,6 @@ func (v *V2RayPoint) RunLoop() (err error) {
defer v.v2rayOP.Unlock()
//Construct Context
v.status.PackageName = v.PackageName
v.status.PackageCodePath = v.PackageCodePath
if !v.status.IsRunning {
v.closeChan = make(chan struct{})
@@ -81,6 +80,7 @@ func (v *V2RayPoint) RunLoop() (err error) {
if !v.dialer.IsVServerReady() {
log.Println("vServer cannot resolved, shutdown")
v.StopLoop()
v.SupportSet.Shutdown()
}
// stop waiting if manually closed
@@ -99,7 +99,9 @@ func (v *V2RayPoint) StopLoop() (err error) {
v.v2rayOP.Lock()
defer v.v2rayOP.Unlock()
if v.status.IsRunning {
close(v.closeChan)
v.shutdownInit()
v.SupportSet.OnEmitStatus(0, "Closed")
}
return
}
@@ -109,16 +111,12 @@ func (v *V2RayPoint) GetIsRunning() bool {
return v.status.IsRunning
}
func (v *V2RayPoint) GetIsTRunning() bool {
return v.status.IsTRunning
}
//Delegate Funcation
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
if v.statsManager == nil {
return 0
}
counter := v.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
counter := v.statsManager.GetCounter(fmt.Sprintf("inbound>>>%s>>>traffic>>>%s", tag, direct))
if counter == nil {
return 0
}
@@ -126,19 +124,24 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
}
func (v *V2RayPoint) shutdownInit() {
close(v.closeChan)
v.statsManager = nil
v.status.IsRunning = false
v.status.Vpoint.Close()
v.status.Vpoint = nil
v.status.IsRunning = false
v.statsManager = nil
v.escorter.EscortingDown()
v.SupportSet.Shutdown()
v.SupportSet.OnEmitStatus(0, "Closed")
}
func (v *V2RayPoint) pointloop() error {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
log.Println("loading v2ray config")
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
if err != nil {
@@ -166,21 +169,6 @@ func (v *V2RayPoint) pointloop() error {
v.SupportSet.Prepare()
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
v.SupportSet.OnEmitStatus(0, "Running")
v.status.IsTRunning = false
if !v.ProxyOnly {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
v.status.IsTRunning = true
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
}
return nil
}
@@ -237,10 +225,17 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
}
func (v V2RayPoint) runTun2socks() error {
shipb := shippedBinarys.FirstRun{Status: v.status}
if err := shipb.CheckAndExport(); err != nil {
log.Println(err)
return err
}
v.escorter.EscortingUp()
go v.escorter.EscortRun(
v.status.GetApp("libtun2socks.so"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
v.status.GetApp("tun2socks"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
v.SupportSet.SendFd)
return nil
}
@@ -0,0 +1,13 @@
Platdep=shippedBinary.386 shippedBinary.amd64 shippedBinary.arm64 shippedBinary.arm
shippedBinaryDep:
go get -u github.com/jteeuwen/go-bindata/...
shippedBinary.%:
go-bindata -nometadata -nomemcopy -pkg shippedBinarys -o ./binary_$*.go -tags $* ArchIndep/ ArchDep/$*/
shippedBinary:shippedBinaryDep $(Platdep)
@echo "Done"
clean:
-rm binary*
@@ -0,0 +1,69 @@
package shippedBinarys
import (
"log"
"os"
"strconv"
"github.com/2dust/AndroidLibV2rayLite/CoreI"
)
type FirstRun struct {
Status *CoreI.Status
}
func (v *FirstRun) checkIfRcExist() error {
datadir := v.Status.GetDataDir()
if _, err := os.Stat(datadir + strconv.Itoa(CoreI.CheckVersion())); !os.IsNotExist(err) {
log.Println("file exists")
return nil
}
IndepDir, err := AssetDir("ArchIndep")
log.Println(IndepDir)
if err != nil {
return err
}
for _, fn := range IndepDir {
log.Println(datadir+"ArchIndep/"+fn)
err := RestoreAsset(datadir, "ArchIndep/"+fn)
log.Println(err)
//GrantPremission
os.Chmod(datadir+"ArchIndep/"+fn, 0700)
log.Println(os.Remove(datadir + fn))
log.Println(os.Symlink(datadir+"ArchIndep/"+fn, datadir + fn))
}
DepDir, err := AssetDir("ArchDep")
log.Println(DepDir)
if err != nil {
return err
}
for _, fn := range DepDir {
DepDir2, err := AssetDir("ArchDep/" + fn)
log.Println("ArchDep/" + fn)
if err != nil {
return err
}
for _, FND := range DepDir2 {
log.Println(datadir+"ArchDep/"+fn+"/"+FND)
RestoreAsset(datadir, "ArchDep/"+fn+"/"+FND)
os.Chmod(datadir+"ArchDep/"+fn+"/"+FND, 0700)
log.Println(os.Remove(datadir + FND))
log.Println(os.Symlink(datadir+"ArchDep/"+fn+"/"+FND, datadir+FND))
}
}
s, _ := os.Create(datadir + strconv.Itoa(CoreI.CheckVersion()))
s.Close()
return nil
}
func (v *FirstRun) CheckAndExport() error {
return v.checkIfRcExist()
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"log"
"os"
v2commlog "github.com/xtls/xray-core/common/log"
v2commlog "v2ray.com/core/common/log"
)
type consoleLogWriter struct {
-29
View File
@@ -1,35 +1,6 @@
# v2rayNG
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-17%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/jelly-bean#android-4.2)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.4.10-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)
[![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn)
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />
</a>
### Usage
#### Geoip and Geosite
v2rayNG release already embedded domain file `geoip.dat` and `geosite.dat`. However it is (probably) not the latest and not the most complete list.
For power user, the embedded files can be easily replaced with the following steps:
1. Launch v2rayNG (v1.4.9+)
2. Find existing geoip.dat and geosite.dat in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
3. Replace them with the latest [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip)
4. Enhanced version can be found in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (recommend to use `geosite:geolocation-!cn` for proxy dns and routing)
5. It is also possible to use third party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
#### See more in our [wiki](https://github.com/2dust/v2rayNG/wiki)
### Development guide
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
The aar can be compiled from the Golang project under AndroidLibV2rayLite folder. For a quick start, read guide for [Go Mobile](https://github.com/golang/go/wiki/Mobile)
and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)
v2rayNG can run on Android Emulators, with minimum Android 5.0
+32 -24
View File
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion buildToolsVer
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileOptions {
targetCompatibility = "8"
@@ -20,19 +20,34 @@ android {
versionName "1.0.2"
}
signingConfigs {
release {
storeFile file("../key.jks")
keyAlias 'ang'
keyPassword '123456'
storePassword '123456'
}
debug {
storeFile file("../key.jks")
keyAlias 'ang'
keyPassword '123456'
storePassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
signingConfig signingConfigs.release
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
signingConfig signingConfigs.release
}
}
@@ -40,10 +55,6 @@ android {
main.java.srcDirs += 'src/main/kotlin'
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
splits {
abi {
enable true
@@ -68,17 +79,11 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.13'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
implementation project(':dpreference')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
// Androidx ktx
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:livedata:1.1.1'
// Android support library
implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion"
@@ -86,19 +91,22 @@ dependencies {
implementation "com.android.support:cardview-v7:$supportLibVersion"
implementation "com.android.support:preference-v7:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:multidex:1.0.3"
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
// DSL
implementation 'com.tencent:mmkv-static:1.0.19'
implementation 'com.google.code.gson:gson:2.8.6'
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'io.reactivex:rxjava:1.3.4'
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
implementation 'io.reactivex:rxkotlin:0.60.0'
implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'com.beust:klaxon:3.0.1'
implementation 'com.android.support:multidex:1.0.3'
implementation(name: 'libv2ray', ext: 'aar')
//implementation(name: 'tun2socks', ext: 'aar')
@@ -118,4 +126,4 @@ repositories {
flatDir {
dirs 'libs'
}
}
}
Binary file not shown.
+3
View File
@@ -44,6 +44,9 @@
static void throwUninitializedPropertyAccessException(java.lang.String);
}
-dontwarn org.jetbrains.anko.internals.**
-keep class org.jetbrains.anko.internals.** { *;}
-dontwarn rx.internal.util.unsafe.**
-keep class rx.internal.util.unsafe.** { *;}
+27 -48
View File
@@ -1,23 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.v2ray.ang">
<supports-screens
android:anyDensity="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
@@ -27,14 +11,12 @@
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<application
android:name=".AngApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:extractNativeLibs="true"
android:theme="@style/AppTheme">
<activity
android:name=".ui.MainActivity"
@@ -42,16 +24,20 @@
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
@@ -60,7 +46,13 @@
android:name=".ui.ServerActivity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.ServerCustomConfigActivity"
android:name=".ui.Server2Activity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.Server3Activity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.Server4Activity"
android:windowSoftInputMode="stateUnchanged" />
<activity android:name=".ui.SettingsActivity" />
<activity android:name=".ui.PerAppProxyActivity" />
@@ -74,11 +66,7 @@
<activity android:name=".ui.SubEditActivity" />
<activity android:name=".ui.ScScannerActivity" />
<activity
android:name=".ui.ScSwitchActivity"
android:excludeFromRecents="true"
android:process=":RunSoLibV2RayDaemon"
android:theme="@style/AppTheme.NoActionBar.Translucent" />
<activity android:name=".ui.ScSwitchActivity" />
<service
android:name=".service.V2RayVpnService"
@@ -95,30 +83,22 @@
android:value="true" />
</service>
<service android:name=".service.V2RayProxyOnlyService"
android:exported="false"
android:label="@string/app_name"
android:process=":RunSoLibV2RayDaemon">
</service>
<!--<receiver android:name=".receiver.WidgetProvider">-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/app_widget_provider" />-->
<receiver android:name=".receiver.WidgetProvider"
android:process=":RunSoLibV2RayDaemon">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.v2ray.ang.action.widget.click" />
<action android:name="com.v2ray.ang.action.activity" />
</intent-filter>
</receiver>
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
<!--<action android:name="com.v2ray.ang.action.widget.click" />-->
<!--</intent-filter>-->
<!--</receiver>-->
<service
android:name=".service.QSTileService"
android:icon="@drawable/ic_v"
android:label="@string/app_tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:process=":RunSoLibV2RayDaemon">
android:name=".service.QSTileService"
android:icon="@drawable/ic_v"
android:label="@string/app_tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -133,8 +113,7 @@
</intent-filter>
</activity>
<receiver android:name=".receiver.TaskerReceiver"
android:process=":RunSoLibV2RayDaemon">
<receiver android:name=".receiver.TaskerReceiver">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter>
@@ -143,4 +122,4 @@
</application>
</manifest>
</manifest>
+136 -181
View File
@@ -1,241 +1,196 @@
amanita_design.samorost3.gp
android
au.com.shiftyjelly.pocketcasts
bbc.mobile.news.ww
be.mygod.vpnhotspot
ch.protonmail.android
co.wanqu.android
com.alphainventor.filemanager
com.amazon.kindle
com.amazon.mshop.android.shopping
com.android.chrome
com.android.providers.downloads
com.android.providers.downloads.ui
com.android.providers.telephony
com.android.settings
com.android.vending
com.android6park.m6park
com.apkpure.aegon
com.apkupdater
com.app.pornhub
com.arthurivanets.owly
com.asahi.tida.tablet
com.authy.authy
com.avmovie
com.ballistiq.artstation
com.binance.dev
com.bitly.app
com.brave.browser
com.brave.browser_beta
com.breel.wallpapers18
com.bvanced.android.youtube
com.chrome.beta
com.chrome.canary
com.chrome.dev
com.cl.newt66y
com.cradle.iitc_mobile
com.cygames.shadowverse
com.devhd.feedly
com.devolver.reigns2
com.discord
com.downloader.video.tumblr
com.driverbrowser
com.dropbox.android
com.duolingo
com.duckduckgo.mobile.android
com.dv.adm
com.estrongs.android.pop
com.estrongs.android.pop.pro
com.evernote
com.facebook.katana
com.facebook.lite
com.facebook.mlite
com.facebook.orca
com.facebook.services
com.facebook.system
com.fastaccess.github
com.felixfilip.scpae
com.fireproofstudios.theroom4
com.firstrowria.pushnotificationtester
com.flyersoft.moonreaderp
com.fooview.android.fooview
com.fvd.eversync
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.gianlu.aria2app
com.github.yeriomin.yalpstore
com.google.android.apps.adm
com.google.android.apps.books
com.google.android.apps.docs
com.google.android.apps.docs.editors.sheets
com.google.android.apps.fitness
com.google.android.apps.googleassistant
com.google.android.apps.googlevoice
com.google.android.apps.hangoutsdialer
com.google.android.apps.inbox
com.google.android.apps.magazines
com.google.android.apps.maps
com.google.android.apps.nbu.files
com.google.android.apps.paidtasks
com.google.android.apps.pdfviewer
com.google.android.apps.photos
com.google.android.apps.plus
com.google.android.apps.translate
com.google.android.gm
com.google.android.gms
com.google.android.gms.setup
com.google.android.googlequicksearchbox
com.google.android.apps.photos
com.google.android.youtube
com.google.android.gm
com.google.android.apps.plus
com.android.vending
com.google.android.inputmethod.latin
com.google.android.apps.paidtasks
com.google.android.keep
com.google.android.gms.setup
com. google.android. apps.magazines
com.google.android.videos
com. google.android.gms
com.google.android.apps.books
com.google.android.music
com.google.android.play.games
com.google.android.gsf
com.google.android.gsf.login
com.app.pornhub
com.spotify.music
org.thunderdog.challegram
com.tumblr
com.twitter.android
com.xda.labs
com.kapp.youtube.final
com.google.android.ims
com.google.android.inputmethod.latin
com.wire
mark.via.gp
com.downloader.video.tumblr
com.sololearn
com.cygames.shadowverse
com.felixfilip.scpae
amanita_design.samorost3.gp
com.devolver.reigns2
com.utopia.pxview
ch.protonmail.android
com.perol.asdpl.pixivez
com.pinterest
com.paypal.android.p2pmobile
com.arthurivanets.owly
com.rubenmayayo.reddit
com.rayark.cytus2
com.rayark.pluto
com.rayark.implosion
com.fireproofstudios.theroom4
com.netflix.mediaclient
com.instagram.android
com.google.android.apps.hangoutsdialer
com.google.android.talk
com.google.android.apps.plus
com.google.android.apps.pdfviewer
com.google.android.apps.magazines
com.google.android.apps.nbu.files
com.evernote
net.tsapps.appsales
com.google.android.apps.translate
com.google.ar.lens
com.google.android.apps.adm
com.google.android.apps.googleassistant
tw.com.gamer.android.activecenter
org.telegram.plus
com.brave.browser
com.breel.wallpapers18
com.teslacoilsw.launcher
com.lastpass.lpandroid
org.kustom.widget
com.fooview.android.fooview
com.google.android.apps.docs
com.google.android.apps.maps
com.facebook.services
com.facebook.system
com.facebook.katana
com.nianticlabs.ingress.prime.qa
com.vanced.android.youtube
com.nianticproject.ingress
com.quoord.tapatalkpro.activity
org.mozilla.firefox
com.reddit.frontpage
com.google.android.apps.fitness
au.com.shiftyjelly.pocketcasts
com.google.android.gms
com.android.providers.telephony
com.resilio.sync
com.google.android.apps.googlevoice
com.discord
com.cradle.iitc_mobile
be.mygod.vpnhotspot
com.alphainventor.filemanager
com.android.providers.downloads
com.apkpure.aegon
com.ballistiq.artstation
com.bitly.app
com.chrome.canary
com.chrome.dev
com.devhd.feedly
com.dropbox.android
com.estrongs.android.pop
com.estrongs.android.pop.pro
com.fastaccess.github
com.firstrowria.pushnotificationtester
com.fvd.eversync
com.gianlu.aria2app
com.github.yeriomin.yalpstore
com.google.android.apps.docs.editors.sheets
com.google.android.instantapps.supervisor
com.google.android.keep
com.google.android.music
com.google.android.ogyoutube
com.google.android.partnersetup
com.google.android.play.games
com.google.android.street
com.google.android.syncadapters.calendar
com.google.android.syncadapters.contacts
com.google.android.talk
com.google.android.tts
com.google.android.videos
com.google.android.youtube
com.google.ar.lens
com.hochan.coldsoup
com.ifttt.ifttt
com.imgur.mobile
com.innologica.inoreader
com.instagram.android
com.instapaper.android
com.jarvanh.vpntether
com.kapp.youtube.final
com.klinker.android.twitter_l
com.lastpass.lpandroid
com.linecorp.linelite
com.lingodeer
com.mediapods.tumbpods
com.mgoogle.android.gms
com.microsoft.emmx
com.microsoft.office.powerpoint
com.microsoft.skydrive
com.mixplorer
com.msd.consumerchinese
com.msd.professionalchinese
com.mss2011c.sharehelper
com.netflix.mediaclient
com.newin.nplayer.pro
com.nianticlabs.ingress.prime.qa
com.nianticproject.ingress
com.ninefolders.hd3
com.ninegag.android.app
com.nintendo.zara
com.nytimes.cn
com.oasisfeng.island
com.ocnt.liveapp.hw
com.orekie.search
com.patreon.android
com.paypal.android.p2pmobile
com.perol.asdpl.pixivez
com.pinterest
com.popularapp.periodcalendar
com.popularapp.videodownloaderforinstagram
com.pushbullet.android
com.quoord.tapatalkpro.activity
com.quora.android
com.rayark.cytus2
com.rayark.implosion
com.rayark.pluto
com.reddit.frontpage
com.resilio.sync
com.rhmsoft.edit
com.rubenmayayo.reddit
com.sec.android.app.sbrowser
com.sec.android.app.sbrowser.beta
com.shanga.walli
com.simplehabit.simplehabitapp
com.slack
com.snaptube.premium
com.sololearn
com.sonelli.juicessh
com.spotify.music
com.tencent.huatuo
com.termux
com.teslacoilsw.launcher
com.theinitium.news
com.thomsonreuters.reuters
com.thunkable.android.hritvik00.freenom
com.topjohnwu.magisk
com.tripadvisor.tripadvisor
com.tumblr
com.twitter.android
com.u91porn
com.u9porn
com.ubisoft.dance.justdance2015companion
com.utopia.pxview
com.valvesoftware.android.steam.communimunity
com.valvesoftware.android.steam.community
com.vanced.android.youtube
com.vimeo.android.videoapp
com.vivaldi.browser
com.vivaldi.browser.snapshot
com.vkontakte.android
com.whatsapp
com.wire
com.wuxiangai.refactor
com.xda.labs
com.xvideos.app
com.yandex.browser
com.yandex.browser.beta
com.yandex.browser.alpha
com.z28j.feel
con.medium.reader
de.apkgrabber
de.robv.android.xposed.installer
dk.tacit.android.foldersync.full
es.rafalense.telegram.themes
es.rafalense.themes
flipboard.app
fm.moon.app
fr.gouv.etalab.mastodon
github.tornaco.xposedmoduletest
idm.internet.download.manager
idm.internet.download.manager.plus
io.github.javiewer
io.github.skyhacker2.magnetsearch
io.va.exposed
it.mvilla.android.fenix2
jp.bokete.app.android
jp.naver.line.android
jp.pxv.android
luo.speedometergpspro
mark.via.gp
me.tshine.easymark
net.teeha.android.url_shortener
net.tsapps.appsales
onion.fire
org.fdroid.fdroid
org.freedownloadmanager.fdm
org.kustom.widget
org.mozilla.fennec_aurora
org.mozilla.fenix
org.mozilla.fenix.nightly
org.mozilla.firefox
org.mozilla.firefox_beta
org.mozilla.focus
org.schabi.newpipe
org.telegram.messenger
org.telegram.multi
org.telegram.plus
org.thunderdog.challegram
org.torproject.android
org.torproject.torbrowser_alpha
org.wikipedia
org.xbmc.kodi
pl.zdunex25.updater
tv.twitch.android.app
tw.com.gamer.android.activecenter
videodownloader.downloadvideo.downloader
uk.co.bbc.learningenglish
com.ted.android
com.quora.android
com.lingodeer
org.wikipedia
com.ninegag.android.app
com.duolingo
com.patreon.android
com.valvesoftware.android.steam.communimunity
co.wanqu.android
jp.bokete.app.android
com.vkontakte.android
com.amazon.mshop.android.shopping
com.ubisoft.dance.justdance2015companion
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.binance.dev
com.asahi.tida.tablet
com.theinitium.news
com.driverbrowser
com.thomsonreuters.reuters
com.nytimes.cn
com.android.providers.downloads.ui
com.avmovie
bbc.mobile.news.ww
org.mozilla.focus
io.github.javiewer
com.sonelli.juicessh
con.medium.reader
com.microsoft.skydrive
com.valvesoftware.android.steam.community
com.nintendo.zara
org.torproject.torbrowser_alpha
tv.twitch.android.app
com.shanga.walli
com.whatsapp
com.wire
com.simplehabit.simplehabitapp
@@ -13,8 +13,8 @@
}
},
"system": {
"statsOutboundUplink": true,
"statsOutboundDownlink": true
"statsInboundUplink": true,
"statsInboundDownlink": true
}
},
"inbounds": [{
@@ -43,8 +43,6 @@ public interface ItemTouchHelperAdapter {
boolean onItemMove(int fromPosition, int toPosition);
void onItemMoveCompleted();
/**
* Called when an item has been dismissed by a swipe.<br/>
* <br/>
@@ -112,8 +112,6 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onItemMoveCompleted();
viewHolder.itemView.setAlpha(ALPHA_FULL);
if (viewHolder instanceof ItemTouchHelperViewHolder) {
Binary file not shown.
Binary file not shown.
@@ -1,29 +1,31 @@
package com.v2ray.ang
import android.support.multidex.MultiDexApplication
import android.support.v7.preference.PreferenceManager
import com.tencent.mmkv.MMKV
import android.app.Application
//import com.squareup.leakcanary.LeakCanary
import com.v2ray.ang.util.AngConfigManager
import me.dozen.dpreference.DPreference
import org.jetbrains.anko.defaultSharedPreferences
class AngApplication : MultiDexApplication() {
class AngApplication : Application() {
companion object {
const val PREF_LAST_VERSION = "pref_last_version"
}
var curIndex = -1 //Current proxy that is opened. (Used to implement restart feature)
var firstRun = false
private set
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
override fun onCreate() {
super.onCreate()
// LeakCanary.install(this)
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
if (firstRun)
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
MMKV.initialize(this)
AngConfigManager.inject(this)
}
}
@@ -6,46 +6,15 @@ package com.v2ray.ang
*/
object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang"
// legacy
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_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 VMESS_PROTOCOL: String = "vmess://"
const val SS_PROTOCOL: String = "ss://"
const val SOCKS_PROTOCOL: String = "socks://"
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
@@ -56,6 +25,9 @@ object AppConfig {
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
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_DIRECT = "direct"
const val TAG_BLOCKED = "block"
@@ -63,7 +35,6 @@ object AppConfig {
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
const val promotionUrl = "https://1.2345345.xyz/ads.html"
const val DNS_AGENT = "1.1.1.1"
@@ -79,4 +50,12 @@ object AppConfig {
const val MSG_STATE_STOP = 4
const val MSG_STATE_STOP_SUCCESS = 41
const val MSG_STATE_RESTART = 5
}
object EConfigType {
val Vmess = 1
val Custom = 2
val Shadowsocks = 3
val Socks = 4
}
}
@@ -1,14 +0,0 @@
package com.v2ray.ang.dto
enum class EConfigType(val value: Int, val protocolScheme: String) {
VMESS(1, "vmess://"),
CUSTOM(2, ""),
SHADOWSOCKS(3, "ss://"),
SOCKS(4, "socks://"),
VLESS(5, "vless://"),
TROJAN(6, "trojan://");
companion object {
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
}
}
@@ -1,10 +0,0 @@
package com.v2ray.ang.dto
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
fun getTestDelayString(): String {
if (testDelayMillis == 0L) {
return ""
}
return testDelayMillis.toString() + "ms"
}
}
@@ -1,69 +0,0 @@
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)
}
}
}
@@ -1,8 +0,0 @@
package com.v2ray.ang.dto
data class SubscriptionItem(
var remarks: String = "",
var url: String = "",
var enabled: Boolean = true,
val addedTime: Long = System.currentTimeMillis()) {
}
@@ -1,446 +1,142 @@
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(
var stats: Any? = null,
val stats: Any?=null,
val log: LogBean,
var policy: PolicyBean?,
val policy: PolicyBean,
val inbounds: ArrayList<InboundBean>,
var outbounds: ArrayList<OutboundBean>,
var dns: DnsBean,
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"
}
val routing: RoutingBean) {
data class LogBean(val access: String,
val error: String,
var loglevel: String?,
val dnsLog: Boolean? = null)
val loglevel: String)
data class InboundBean(
var tag: String,
var port: Int,
var protocol: String,
var listen: String? = null,
val settings: Any? = null,
val sniffing: SniffingBean?,
val streamSettings: Any? = null,
val allocate: Any? = null) {
var listen: String?=null,
val settings: InSettingsBean,
val sniffing: SniffingBean?) {
data class InSettingsBean(val auth: String? = null,
val udp: Boolean? = null,
val userLevel: Int? = null,
val userLevel: Int? =null,
val address: String? = null,
val port: Int? = null,
val network: String? = null)
data class SniffingBean(var enabled: Boolean,
val destOverride: ArrayList<String>,
val metadataOnly: Boolean? = null)
val destOverride: List<String>)
}
data class OutboundBean(val tag: String = "proxy",
data class OutboundBean(val tag: String,
var protocol: String,
var settings: OutSettingsBean? = null,
var streamSettings: StreamSettingsBean? = null,
val proxySettings: Any? = null,
val sendThrough: String? = null,
val mux: MuxBean? = MuxBean(false)) {
var settings: OutSettingsBean?,
var streamSettings: StreamSettingsBean?,
var mux: MuxBean?) {
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
var servers: List<ServersBean>? = null,
/*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 OutSettingsBean(var vnext: List<VnextBean>?,
var servers: List<ServersBean>?,
var response: Response) {
data class VnextBean(var address: String = "",
var port: Int = DEFAULT_PORT,
data class VnextBean(var address: String,
var port: Int,
var users: List<UsersBean>) {
data class UsersBean(var id: String = "",
var alterId: Int? = null,
var security: String = DEFAULT_SECURITY,
var level: Int = DEFAULT_LEVEL,
var encryption: String = "",
var flow: String = "")
data class UsersBean(var id: String,
var alterId: Int,
var security: String,
var level: Int)
}
data class ServersBean(var address: String = "",
var method: String = "chacha20-poly1305",
var ota: Boolean = false,
var password: String = "",
var port: Int = DEFAULT_PORT,
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 ServersBean(var address: String,
var method: String,
var ota: Boolean,
var password: String,
var port: Int,
var level: Int)
data class Response(var type: String)
}
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
var security: String = "",
var tcpSettings: TcpSettingsBean? = null,
var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean? = null,
var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? = null,
var xtlsSettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null,
val dsSettings: Any? = null,
val sockopt: Any? = null
data class StreamSettingsBean(var network: String,
var security: String,
var tcpSettings: TcpsettingsBean?,
var kcpsettings: KcpsettingsBean?,
var wssettings: WssettingsBean?,
var httpsettings: HttpsettingsBean?,
var tlssettings: TlssettingsBean?,
var quicsettings: QuicsettingBean?
) {
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
val acceptProxyProtocol: Boolean? = null) {
data class TcpsettingsBean(var connectionReuse: Boolean = true,
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none",
var request: RequestBean? = 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)
}
}
var request: Any? = null,
var response: Any? = null)
}
data class KcpSettingsBean(var mtu: Int = 1350,
var tti: Int = 50,
data class KcpsettingsBean(var mtu: Int = 1350,
var tti: Int = 20,
var uplinkCapacity: Int = 12,
var downlinkCapacity: Int = 100,
var congestion: Boolean = false,
var readBufferSize: Int = 1,
var writeBufferSize: Int = 1,
var header: HeaderBean = HeaderBean(),
var seed: String? = null) {
data class HeaderBean(var type: String = "none")
}
data class WsSettingsBean(var path: String = "",
var headers: HeadersBean = HeadersBean(),
val maxEarlyData: Int? = null,
val useBrowserForwarding: Boolean? = null,
val acceptProxyProtocol: Boolean? = null) {
data class HeadersBean(var Host: String = "")
}
data class HttpSettingsBean(var host: List<String> = ArrayList(),
var path: String = "")
data class TlsSettingsBean(var allowInsecure: Boolean = false,
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",
var key: String = "",
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none")
}
data class GrpcSettingsBean(var serviceName: String = "",
val multiMode: Boolean? = null)
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: 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.serviceName = path ?: ""
sni = host ?: ""
grpcSettings = grpcSetting
}
}
return sni
data class WssettingsBean(var connectionReuse: Boolean = true,
var path: String = "",
var headers: HeadersBean = HeadersBean()) {
data class HeadersBean(var Host: String = "")
}
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 HttpsettingsBean(var host: List<String> = ArrayList<String>(), var path: String = "")
data class TlssettingsBean(var allowInsecure: Boolean = true,
var serverName: String = "")
data class QuicsettingBean(var security: String = "none",
var key: String = "",
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none")
}
}
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
fun getServerAddress(): String? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.address
} 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 null
}
fun getServerPort(): Int? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.port
} 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 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("",
"",
grpcSetting.serviceName)
}
else -> null
}
}
return null
}
data class MuxBean(var enabled: Boolean)
}
data class DnsBean(var servers: List<Any>? = 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 DnsBean(var servers: List<String>)
data class DnsBean(var servers: List<Any>?=null,
var hosts: Map<String, String>?=null
) {
data class ServersBean(var address: String = "",
var port: Int? = null,
var domains: List<String>? = null,
var expectIPs: List<String>? = null,
val clientIp: String? = null)
var port: Int = 0,
var domains: List<String>?)
}
data class RoutingBean(var domainStrategy: String,
val domainMatcher: String? = null,
var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null) {
var rules: ArrayList<RulesBean>) {
data class RulesBean(var type: String = "",
var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null,
var outboundTag: String = "",
var balancerTag: String? = null,
var port: 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
)
var inboundTag: ArrayList<String>? = null)
}
data class PolicyBean(var levels: Map<String, LevelBean>,
var system: Any? = null) {
var system: Any?=null) {
data class LevelBean(
var handshake: Int? = null,
var connIdle: Int? = null,
var uplinkOnly: Int? = null,
var downlinkOnly: Int? = null,
val statsUserUplink: Boolean? = null,
val statsUserDownlink: Boolean? = null,
var bufferSize: Int? = null)
var handshake: Int? = null,
var connIdle: Int? = null,
var uplinkOnly: Int? = null,
var downlinkOnly: 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)
}
}
}
@@ -10,5 +10,4 @@ data class VmessQRCode(var v: String = "",
var type: String = "",
var host: String = "",
var path: String = "",
var tls: String = "",
var sni: String = "")
var tls: String = "")
@@ -2,9 +2,8 @@ package com.v2ray.ang.extension
import android.content.Context
import android.os.Build
import android.widget.Toast
import com.v2ray.ang.AngApplication
import me.drakeet.support.toast.ToastCompat
import me.dozen.dpreference.DPreference
import org.json.JSONObject
import java.net.URLConnection
@@ -15,19 +14,11 @@ import java.net.URLConnection
val Context.v2RayApplication: AngApplication
get() = applicationContext as AngApplication
inline fun Context.toast(message: Int): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
val Context.defaultDPreference: DPreference
get() = v2RayApplication.defaultDPreference
inline fun Context.toast(message: CharSequence): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
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) }
const val threshold = 1000
@@ -36,37 +27,34 @@ const val divisor = 1024F
fun Long.toSpeedString() = toTrafficString() + "/s"
fun Long.toTrafficString(): String {
if (this == 0L)
return "\t\t\t0\t B"
if (this < threshold)
return "${this.toFloat().toShortString()}\t B"
return "$this B"
val kib = this / divisor
if (kib < threshold)
return "${kib.toShortString()}\t KB"
return "${kib.toShortString()} KB"
val mib = kib / divisor
if (mib < threshold)
return "${mib.toShortString()}\t MB"
return "${mib.toShortString()} MB"
val gib = mib / divisor
if (gib < threshold)
return "${gib.toShortString()}\t GB"
return "${gib.toShortString()} GB"
val tib = gib / divisor
if (tib < threshold)
return "${tib.toShortString()}\t TB"
return "${tib.toShortString()} TB"
val pib = tib / divisor
if (pib < threshold)
return "${pib.toShortString()}\t PB"
return "${pib.toShortString()} PB"
return ""
}
private fun Float.toShortString(): String {
val s = "%.2f".format(this)
val s = toString()
if (s.length <= 4)
return s
return s.substring(0, 4).removeSuffix(".")
@@ -0,0 +1,10 @@
package com.v2ray.ang.extension
import android.preference.Preference
fun Preference.onClick(listener: () -> Unit) {
setOnPreferenceClickListener {
listener()
true
}
}
@@ -5,16 +5,11 @@ import android.content.Context
import android.content.Intent
import android.text.TextUtils
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
class TaskerReceiver : BroadcastReceiver() {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
override fun onReceive(context: Context, intent: Intent?) {
try {
@@ -26,10 +21,9 @@ class TaskerReceiver : BroadcastReceiver() {
return
} else if (switch) {
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
Utils.startVServiceFromToggle(context)
Utils.startVService(context)
} else {
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
V2RayServiceManager.startV2Ray(context)
Utils.startVService(context, guid)
}
} else {
Utils.stopVService(context)
@@ -3,14 +3,14 @@ package com.v2ray.ang.receiver
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.RemoteViews
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
class WidgetProvider : AppWidgetProvider() {
/**
@@ -18,20 +18,11 @@ class WidgetProvider : AppWidgetProvider() {
*/
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
}
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
val intent = Intent(context, WidgetProvider::class.java)
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
val intent = Intent(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
if (isRunning) {
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme)
} else {
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_grey)
}
for (appWidgetId in appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
@@ -39,29 +30,21 @@ class WidgetProvider : AppWidgetProvider() {
}
/**
* 接收窗口小部件发送的广播
* 接收窗口小部件点击时发送的广播
*/
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) {
if (V2RayServiceManager.v2rayPoint.isRunning) {
val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")
if (isRunning) {
// context.toast(R.string.toast_services_stop)
Utils.stopVService(context)
} else {
Utils.startVServiceFromToggle(context)
}
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
AppWidgetManager.getInstance(context)?.let { manager ->
when (intent.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
true)
}
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
false)
}
}
// context.toast(R.string.toast_services_start)
Utils.startVService(context)
}
}
}
}
@@ -6,15 +6,19 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.Icon
import android.net.VpnService
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
import java.lang.ref.SoftReference
@TargetApi(Build.VERSION_CODES.N)
class QSTileService : TileService() {
@@ -25,10 +29,11 @@ class QSTileService : TileService() {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
} else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
qsTile?.label = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
}
qsTile?.updateTile()
}
@@ -51,7 +56,11 @@ class QSTileService : TileService() {
super.onClick()
when (qsTile.state) {
Tile.STATE_INACTIVE -> {
Utils.startVServiceFromToggle(this)
val intent = VpnService.prepare(this)
if (intent == null)
if (!Utils.startVService(this)) {
toast(R.string.app_tile_first_use)
}
}
Tile.STATE_ACTIVE -> {
Utils.stopVService(this)
@@ -84,4 +93,4 @@ class QSTileService : TileService() {
}
}
}
}
}
@@ -1,14 +0,0 @@
package com.v2ray.ang.service
import android.app.Service
interface ServiceControl {
fun getService(): Service
fun startService(parameters: String)
fun stopService()
fun vpnProtect(socket: Int): Boolean
}
@@ -1,43 +0,0 @@
package com.v2ray.ang.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import java.lang.ref.SoftReference
class V2RayProxyOnlyService : Service(), ServiceControl {
override fun onCreate() {
super.onCreate()
V2RayServiceManager.serviceControl = SoftReference(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
V2RayServiceManager.startV2rayPoint()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
V2RayServiceManager.stopV2rayPoint()
}
override fun getService(): Service {
return this
}
override fun startService(parameters: String) {
// do nothing
}
override fun stopService() {
stopSelf()
}
override fun vpnProtect(socket: Int): Boolean {
return true
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
@@ -1,371 +0,0 @@
package com.v2ray.ang.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq
import libv2ray.Libv2ray
import libv2ray.V2RayPoint
import libv2ray.V2RayVPNServiceSupportsSet
import rx.Observable
import rx.Subscription
import java.lang.ref.SoftReference
import kotlin.math.min
object V2RayServiceManager {
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
private const val NOTIFICATION_ICON_THRESHOLD = 3000
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
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
set(value) {
field = value
val context = value?.get()?.getService()?.applicationContext
context?.let {
v2rayPoint.packageName = Utils.packagePath(context)
v2rayPoint.packageCodePath = context.applicationInfo.nativeLibraryDir + "/"
Seq.setContext(context)
}
}
var currentConfig: ServerConfig? = null
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context) {
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{
context.toast(R.string.toast_services_start)
}
val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
Intent(context.applicationContext, V2RayVpnService::class.java)
} else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
private class V2RayCallback : V2RayVPNServiceSupportsSet {
override fun shutdown(): Long {
val serviceControl = serviceControl?.get() ?: return -1
// called by go
// shutdown the whole vpn service
return try {
serviceControl.stopService()
0
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
-1
}
}
override fun prepare(): Long {
return 0
}
override fun protect(l: Long): Long {
val serviceControl = serviceControl?.get() ?: return 0
return if (serviceControl.vpnProtect(l.toInt())) 0 else 1
}
override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0
}
override fun setup(s: String): Long {
val serviceControl = serviceControl?.get() ?: return -1
//Logger.d(s)
return try {
serviceControl.startService(s)
lastQueryTime = System.currentTimeMillis()
startSpeedNotification()
0
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
-1
}
}
}
fun startV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (!v2rayPoint.isRunning) {
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
if (!result.status)
return
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
service.registerReceiver(mMsgReceive, mFilter)
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
currentConfig = config
v2rayPoint.enableLocalDNS = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
v2rayPoint.forwardIpv6 = settingsStorage?.decodeBool(AppConfig.PREF_FORWARD_IPV6) ?: false
v2rayPoint.proxyOnly = settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" != "VPN"
try {
v2rayPoint.runLoop()
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
}
}
}
fun stopV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
if (v2rayPoint.isRunning) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
cancelNotification()
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
}
private class ReceiveMessageHandler : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
val serviceControl = serviceControl?.get() ?: return
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// nothing to do
}
AppConfig.MSG_STATE_START -> {
// nothing to do
}
AppConfig.MSG_STATE_STOP -> {
serviceControl.stopService()
}
AppConfig.MSG_STATE_RESTART -> {
startV2rayPoint()
}
}
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
startSpeedNotification()
}
}
}
}
private fun showNotification() {
val service = serviceControl?.get()?.getService() ?: return
val startMainIntent = Intent(service, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(service,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_v)
.setContentTitle(currentConfig?.remarks)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
.setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_close_grey_800_24dp,
service.getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent)
//.build()
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
service.startForeground(NOTIFICATION_ID, mBuilder?.build())
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "RAY_NG_M_CH_ID"
val channelName = "V2rayNG Background Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
chan.lightColor = Color.DKGRAY
chan.importance = NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
getNotificationManager()?.createNotificationChannel(chan)
return channelId
}
fun cancelNotification() {
val service = serviceControl?.get()?.getService() ?: return
service.stopForeground(true)
mBuilder = null
mSubscription?.unsubscribe()
mSubscription = null
}
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
if (mBuilder != null) {
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
mBuilder?.setSmallIcon(R.drawable.ic_v)
} else if (proxyTraffic > directTraffic) {
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
} else {
mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
}
mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
mBuilder?.setContentText(contentText) // Emui4.1 need content text even if style is set as BigTextStyle
getNotificationManager()?.notify(NOTIFICATION_ID, mBuilder?.build())
}
}
private fun getNotificationManager(): NotificationManager? {
if (mNotificationManager == null) {
val service = serviceControl?.get()?.getService() ?: return null
mNotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
return mNotificationManager
}
fun startSpeedNotification() {
if (mSubscription == null &&
v2rayPoint.isRunning &&
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
var lastZeroSpeed = false
val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT)
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
val queryTime = System.currentTimeMillis()
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
var proxyTotal = 0L
val text = StringBuilder()
outboundTags?.forEach {
val up = v2rayPoint.queryStats(it, "uplink")
val down = v2rayPoint.queryStats(it, "downlink")
if (up + down > 0) {
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
proxyTotal += up + down
}
}
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds)
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
}
lastZeroSpeed = zeroSpeed
lastQueryTime = queryTime
}
}
}
private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
var n = name ?: "no tag"
n = n.substring(0, min(n.length, 6))
text.append(n)
for (i in n.length..6 step 2) {
text.append("\t")
}
text.append("${up.toLong().toSpeedString()}${down.toLong().toSpeedString()}\n")
}
fun stopSpeedNotification() {
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
updateNotification(currentConfig?.remarks, 0, 0)
}
}
}
@@ -1,30 +1,69 @@
package com.v2ray.ang.service
import android.app.*
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.*
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.StrictMode
import android.net.VpnService
import android.os.*
import android.support.annotation.RequiresApi
import android.util.Log
import com.tencent.mmkv.MMKV
import android.support.v4.app.NotificationCompat
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import libv2ray.V2RayVPNServiceSupportsSet
import rx.Observable
import rx.Subscription
import java.net.InetAddress
import java.io.IOException
import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
import java.lang.ref.SoftReference
import android.os.Build
import android.annotation.TargetApi
import android.util.Log
import org.jetbrains.anko.doAsync
class V2RayVpnService : VpnService(), ServiceControl {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
class V2RayVpnService : VpnService() {
companion object {
const val NOTIFICATION_ID = 1
const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
fun startV2Ray(context: Context) {
val intent = Intent(context.applicationContext, V2RayVpnService::class.java)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}
private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private lateinit var configContent: String
private lateinit var mInterface: ParcelFileDescriptor
val fd: Int get() = mInterface.fd
private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null
/**
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
@@ -35,38 +74,36 @@ class V2RayVpnService : VpnService(), ServiceControl {
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkRequest by lazy {
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
}
@TargetApi(28)
private val defaultNetworkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkCallback by lazy {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
setUnderlyingNetworks(arrayOf(network))
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
// it's a good idea to refresh capabilities
setUnderlyingNetworks(arrayOf(network))
}
override fun onLost(network: Network) {
setUnderlyingNetworks(null)
}
@TargetApi(28)
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
setUnderlyingNetworks(arrayOf(network))
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
// it's a good idea to refresh capabilities
setUnderlyingNetworks(arrayOf(network))
}
override fun onLost(network: Network) {
setUnderlyingNetworks(null)
}
}
private var listeningForDefaultNetwork = false
override fun onCreate() {
super.onCreate()
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
V2RayServiceManager.serviceControl = SoftReference(this)
v2rayPoint.packageName = Utils.packagePath(applicationContext)
Seq.setContext(applicationContext)
}
override fun onRevoke() {
@@ -80,12 +117,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
override fun onDestroy() {
super.onDestroy()
stopV2Ray()
cancelNotification()
}
private fun setup(parameters: String) {
fun setup(parameters: String) {
val prepare = prepare(this)
val prepare = VpnService.prepare(this)
if (prepare != null) {
return
}
@@ -93,8 +130,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
// If the old interface has exactly the same parameters, use it!
// Configure a builder while parsing the parameters.
val builder = Builder()
val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
parameters.split(" ")
.map { it.split(",") }
@@ -103,39 +139,24 @@ class V2RayVpnService : VpnService(), ServiceControl {
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
's' -> builder.addSearchDomain(it[1])
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
'r' -> {
if (routingMode == "1" || routingMode == "3") {
if (it[1] == "::") { //not very elegant, should move Vpn setting in Kotlin, simplify go code
builder.addRoute("2000::", 3)
} else {
resources.getStringArray(R.array.bypass_private_ip_address).forEach { cidr ->
val addr = cidr.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}
}
} else {
builder.addRoute(it[1], Integer.parseInt(it[2]))
}
}
'r' -> builder.addRoute(it[1], Integer.parseInt(it[2]))
'd' -> builder.addDnsServer(it[1])
}
}
if(!enableLocalDns) {
Utils.getVpnDnsServers()
.forEach {
if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it)
}
}
Utils.getRemoteDnsServers(defaultDPreference)
.forEach {
builder.addDnsServer(it)
}
}
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
val apps = defaultDPreference.getPrefStringSet(PerAppProxyActivity.PREF_PER_APP_PROXY_SET, null)
val bypassApps = defaultDPreference.getPrefBoolean(PerAppProxyActivity.PREF_BYPASS_APPS, false)
apps?.forEach {
try {
if (bypassApps)
@@ -152,42 +173,33 @@ class V2RayVpnService : VpnService(), ServiceControl {
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
} catch (e: Exception) {
e.printStackTrace()
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false)
if (Build.VERSION.SDK_INT >= 28) {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
}
// Create a new interface using the builder and save the parameters.
try {
mInterface = builder.establish()!!
} catch (e: Exception) {
// non-nullable lateinit var
e.printStackTrace()
stopV2Ray()
}
mInterface = builder.establish()
sendFd()
startSpeedNotification()
}
private fun sendFd() {
fun shutdown() {
stopV2Ray(true)
}
fun sendFd() {
val fd = mInterface.fileDescriptor
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
GlobalScope.launch(Dispatchers.IO) {
doAsync {
var tries = 0
while (true) try {
Thread.sleep(1000L shl tries)
Log.d(packageName, "sendFd tries: $tries")
Thread.sleep(50L shl tries)
Log.d(packageName, "sendFd tries: " + tries.toString())
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
@@ -203,57 +215,278 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
V2RayServiceManager.startV2rayPoint()
startV2ray()
return START_STICKY
//return super.onStartCommand(intent, flags, startId)
}
private fun startV2ray() {
if (!v2rayPoint.isRunning) {
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
registerReceiver(mMsgReceive, mFilter)
} catch (e: Exception) {
}
configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
v2rayPoint.configureFileContent = configContent
v2rayPoint.enableLocalDNS = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
v2rayPoint.forwardIpv6 = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
v2rayPoint.domainName = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
try {
v2rayPoint.runLoop()
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
}
}
// showNotification()
}
private fun stopV2Ray(isForced: Boolean = true) {
// val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "")
// val emptyInfo = VpnNetworkInfo()
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
// saveVpnNetworkInfo(configName, info)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
try {
if (Build.VERSION.SDK_INT >= 28) {
if (listeningForDefaultNetwork) {
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
} catch (ignored: Exception) {
// ignored
listeningForDefaultNetwork = false
}
}
if (v2rayPoint.isRunning) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
}
V2RayServiceManager.stopV2rayPoint()
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "")
cancelNotification()
if (isForced) {
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
//It's strage but true.
//This can be verified by putting stopself() behind and call stopLoop and startLoop
//in a row for several times. You will find that later created v2ray core report port in use
//which means the first v2ray core somehow failed to stop and release the port.
stopSelf()
try {
unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
}
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
stopSelf()
}
}
override fun getService(): Service {
return this
private fun showNotification() {
val startMainIntent = Intent(applicationContext, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(applicationContext,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(applicationContext,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
mBuilder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_v)
.setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
.setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_close_grey_800_24dp,
getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent)
//.build()
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
startForeground(NOTIFICATION_ID, mBuilder?.build())
}
override fun startService(parameters: String) {
setup(parameters)
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "RAY_NG_M_CH_ID"
val channelName = "V2rayNG Background Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
chan.lightColor = Color.DKGRAY
chan.importance = NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
getNotificationManager().createNotificationChannel(chan)
return channelId
}
override fun stopService() {
stopV2Ray(true)
private fun cancelNotification() {
stopForeground(true)
mBuilder = null
mSubscription?.unsubscribe()
mSubscription = null
}
override fun vpnProtect(socket: Int): Boolean {
return protect(socket)
private fun updateNotification(contentText: String) {
if (mBuilder != null) {
mBuilder?.setContentTitle(contentText)
getNotificationManager().notify(NOTIFICATION_ID, mBuilder?.build())
}
}
private fun getNotificationManager(): NotificationManager {
if (mNotificationManager == null) {
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
return mNotificationManager!!
}
fun startSpeedNotification() {
if (mSubscription == null &&
v2rayPoint.isRunning &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
var last_zero_speed = false
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
val uplink = v2rayPoint.queryStats("socks", "uplink")
val downlink = v2rayPoint.queryStats("socks", "downlink")
val zero_speed = (uplink == 0L && downlink == 0L)
if (!zero_speed || !last_zero_speed) {
updateNotification("${cf_name}${(uplink / 3).toSpeedString()}${(downlink / 3).toSpeedString()}")
}
last_zero_speed = zero_speed
}
}
}
fun stopSpeedNotification() {
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cf_name)
}
}
private inner class V2RayCallback : V2RayVPNServiceSupportsSet {
override fun shutdown(): Long {
// called by go
// shutdown the whole vpn service
try {
this@V2RayVpnService.shutdown()
return 0
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
}
override fun prepare(): Long {
return 0
}
override fun protect(l: Long) = (if (this@V2RayVpnService.protect(l.toInt())) 0 else 1).toLong()
override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0
}
override fun setup(s: String): Long {
//Logger.d(s)
try {
this@V2RayVpnService.setup(s)
return 0
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
}
override fun sendFd(): Long {
try {
this@V2RayVpnService.sendFd()
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
return 0
}
}
private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService)
private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() {
internal var mReference: SoftReference<V2RayVpnService> = SoftReference(vpnService)
override fun onReceive(ctx: Context?, intent: Intent?) {
val vpnService = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
val isRunning = vpnService?.v2rayPoint!!.isRunning
&& VpnService.prepare(vpnService) == null
if (isRunning) {
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// vpnService?.mMsgSend = null
}
AppConfig.MSG_STATE_START -> {
//nothing to do
}
AppConfig.MSG_STATE_STOP -> {
vpnService?.stopV2Ray()
}
AppConfig.MSG_STATE_RESTART -> {
vpnService?.startV2ray()
}
}
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
vpnService?.stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
vpnService?.startSpeedNotification()
}
}
}
}
}
@@ -0,0 +1,206 @@
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.InappBuyActivity
import com.v2ray.ang.R
import org.jetbrains.anko.startActivity
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.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> {
startActivity<LogcatActivity>()
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 (MainActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.server_profile)
} else if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.sub_setting)
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.settings)
}
}
}
@@ -1,19 +1,17 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.method.ScrollingMovementMethod
import android.view.Menu
import android.view.MenuItem
import android.view.View
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_logcat.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
import org.jetbrains.anko.uiThread
import java.io.IOException
import java.util.LinkedHashSet
@@ -26,22 +24,15 @@ class LogcatActivity : BaseActivity() {
title = getString(R.string.title_logcat)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
logcat(false)
logcat()
}
private fun logcat(shouldFlushLog: Boolean) {
private fun logcat() {
try {
pb_waiting.visibility = View.VISIBLE
GlobalScope.launch(Dispatchers.Default) {
if (shouldFlushLog) {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-c")
val process = Runtime.getRuntime().exec(lst.toTypedArray())
process.waitFor()
}
doAsync {
val lst = LinkedHashSet<String>()
lst.add("logcat")
lst.add("-d")
@@ -54,15 +45,13 @@ class LogcatActivity : BaseActivity() {
// InputStreamReader(process.inputStream))
// val allText = bufferedReader.use(BufferedReader::readText)
val allText = process.inputStream.bufferedReader().use { it.readText() }
launch(Dispatchers.Main) {
uiThread {
tv_logcat.text = allText
tv_logcat.movementMethod = ScrollingMovementMethod()
pb_waiting.visibility = View.GONE
Handler(Looper.getMainLooper()).post { sv_logcat.fullScroll(View.FOCUS_DOWN) }
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}
@@ -77,10 +66,7 @@ class LogcatActivity : BaseActivity() {
toast(R.string.toast_success)
true
}
R.id.delete -> {
logcat(true)
true
}
else -> super.onOptionsItemSelected(item)
}
}
@@ -1,45 +1,38 @@
package com.v2ray.ang.ui
import android.Manifest
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.content.*
import android.net.Uri
import android.net.VpnService
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_main.*
import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.V2rayConfigUtil
import org.jetbrains.anko.*
import java.lang.ref.SoftReference
import java.net.URL
import android.content.IntentFilter
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import me.drakeet.support.toast.ToastCompat
//import com.v2ray.ang.InappBuyActivity
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.net.URL
import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager.configs
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
companion object {
@@ -49,11 +42,22 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private const val REQUEST_SCAN_URL = 3
}
var isRunning = false
set(value) {
field = value
adapter.changeable = !value
if (value) {
fab.imageResource = R.drawable.ic_v
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.imageResource = R.drawable.ic_v_idle
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
}
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
val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -62,23 +66,28 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setSupportActionBar(toolbar)
fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
if (isRunning) {
Utils.stopVService(this)
} else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
} else {
val intent = VpnService.prepare(this)
if (intent == null) {
startV2Ray()
} else {
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
}
} else {
startV2Ray()
}
}
layout_test.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
if (isRunning) {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
tv_test_state.text = getString(R.string.connection_test_testing)
mainViewModel.testCurrentServerRealPing()
doAsync {
val result = Utils.testConnection(this@MainActivity, socksPort)
uiThread {
tv_test_state.text = Utils.getEditable(result)
}
}
} else {
// tv_test_state.text = getString(R.string.connection_test_fail)
}
@@ -98,68 +107,43 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver()
migrateLegacy()
}
private fun setupViewModelObserver() {
mainViewModel.updateListAction.observe(this, {
val index = it ?: return@observe
if (index >= 0) {
adapter.notifyItemChanged(index)
} else {
adapter.notifyDataSetChanged()
}
})
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
mainViewModel.isRunning.observe(this, {
val isRunning = it ?: return@observe
adapter.isRunning = isRunning
if (isRunning) {
fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected)
layout_test.isFocusable = true
} else {
fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected)
layout_test.isFocusable = false
}
hideCircle()
})
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() {
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
if (AngConfigManager.configs.index < 0) {
return
}
showCircle()
// toast(R.string.toast_services_start)
V2RayServiceManager.startV2Ray(this)
hideCircle()
if (!Utils.startVService(this)) {
hideCircle()
}
}
override fun onStart() {
super.onStart()
isRunning = false
// val intent = Intent(this.applicationContext, V2RayVpnService::class.java)
// intent.`package` = AppConfig.ANG_PACKAGE
// bindService(intent, mConnection, BIND_AUTO_CREATE)
mMsgReceive = ReceiveMessageHandler(this@MainActivity)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onStop() {
super.onStop()
if (mMsgReceive != null) {
unregisterReceiver(mMsgReceive)
mMsgReceive = null
}
}
public override fun onResume() {
super.onResume()
mainViewModel.reloadServerList()
adapter.updateConfigList()
}
public override fun onPause() {
@@ -178,8 +162,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
}
REQUEST_FILE_CHOOSER -> {
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
if (resultCode == RESULT_OK) {
val uri = data!!.data
readContentFromUri(uri)
}
}
@@ -205,18 +189,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
R.id.import_manually_vmess -> {
startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
setClass(this, ServerActivity::class.java))
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning)
adapter.updateConfigList()
true
}
R.id.import_manually_ss -> {
startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
setClass(this, ServerActivity::class.java))
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning)
adapter.updateConfigList()
true
}
R.id.import_manually_socks -> {
startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
setClass(this, ServerActivity::class.java))
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning)
adapter.updateConfigList()
true
}
R.id.import_config_custom_clipboard -> {
@@ -247,7 +231,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.export_all -> {
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
if (AngConfigManager.shareAll2Clipboard() == 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
@@ -256,7 +240,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.ping_all -> {
mainViewModel.testAllTcping()
for (k in 0 until configs.vmess.count()) {
configs.vmess[k].testResult = ""
adapter.updateConfigList()
}
for (k in 0 until configs.vmess.count()) {
if (configs.vmess[k].configType != AppConfig.EConfigType.Custom) {
doAsync {
configs.vmess[k].testResult = Utils.tcping(configs.vmess[k].address, configs.vmess[k].port)
uiThread {
adapter.updateSelectedItem(k)
}
}
}
}
true
}
@@ -285,7 +282,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
startActivityForResult<ScannerActivity>(requestCode)
else
toast(R.string.toast_permission_denied)
}
@@ -309,13 +306,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
fun importBatchConfig(server: String?, subid: String = "") {
var count = AngConfigManager.importBatchConfig(server, subid)
if (count <= 0) {
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid)
}
val count = AngConfigManager.importBatchConfig(server, subid)
if (count > 0) {
toast(R.string.toast_success)
mainViewModel.reloadServerList()
adapter.updateConfigList()
} else {
toast(R.string.toast_failure)
}
@@ -374,14 +368,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
toast(R.string.toast_invalid_url)
return false
}
GlobalScope.launch(Dispatchers.IO) {
val configText = try {
URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
doAsync {
val configText = URL(url).readText()
uiThread {
importCustomizeConfig(configText)
}
}
@@ -399,27 +388,24 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
: Boolean {
try {
toast(R.string.title_sub_update)
MmkvManager.decodeSubscriptions().forEach {
if (TextUtils.isEmpty(it.first)
|| TextUtils.isEmpty(it.second.remarks)
|| TextUtils.isEmpty(it.second.url)
val subItem = AngConfigManager.configs.subItem
for (k in 0 until subItem.count()) {
if (TextUtils.isEmpty(subItem[k].id)
|| TextUtils.isEmpty(subItem[k].remarks)
|| TextUtils.isEmpty(subItem[k].url)
) {
return@forEach
continue
}
val url = it.second.url
val id = subItem[k].id
val url = subItem[k].url
if (!Utils.isValidUrl(url)) {
return@forEach
continue
}
Log.d("Main", url)
GlobalScope.launch(Dispatchers.IO) {
val configText = try {
URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
importBatchConfig(Utils.decode(configText), it.first)
doAsync {
val configText = URL(url).readText()
uiThread {
importBatchConfig(Utils.decode(configText), id)
}
}
}
@@ -456,9 +442,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.subscribe {
if (it) {
try {
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
val inputStream = contentResolver.openInputStream(uri)
val configText = inputStream.bufferedReader().readText()
importCustomizeConfig(configText)
} catch (e: Exception) {
e.printStackTrace()
}
@@ -471,19 +457,20 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import customize config
*/
fun importCustomizeConfig(server: String?) {
try {
if (server == null || TextUtils.isEmpty(server)) {
toast(R.string.toast_none_data)
return
}
mainViewModel.appendCustomConfigServer(server)
toast(R.string.toast_success)
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()
if (server == null) {
return
}
if (!V2rayConfigUtil.isValidConfig(server)) {
toast(R.string.toast_config_file_invalid)
return
}
val resId = AngConfigManager.importCustomizeConfig(server)
if (resId > 0) {
toast(resId)
} else {
toast(R.string.toast_success)
adapter.updateConfigList()
}
}
// val mConnection = object : ServiceConnection {
@@ -495,6 +482,35 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// }
// }
private
var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(activity: MainActivity) : BroadcastReceiver() {
internal var mReference: SoftReference<MainActivity> = SoftReference(activity)
override fun onReceive(ctx: Context?, intent: Intent?) {
val activity = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
activity?.isRunning = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
activity?.isRunning = false
}
AppConfig.MSG_STATE_START_SUCCESS -> {
activity?.toast(R.string.toast_services_success)
activity?.isRunning = true
}
AppConfig.MSG_STATE_START_FAILURE -> {
activity?.toast(R.string.toast_services_failure)
activity?.isRunning = false
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
activity?.isRunning = false
}
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
moveTaskToBack(false)
@@ -533,11 +549,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
when (item.itemId) {
//R.id.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> {
startActivity(Intent(this, SubSettingActivity::class.java))
startActivity<SubSettingActivity>()
}
R.id.settings -> {
startActivity(Intent(this, SettingsActivity::class.java)
.putExtra("isRunning", mainViewModel.isRunning.value == true))
startActivity<SettingsActivity>("isRunning" to isRunning)
}
R.id.feedback -> {
Utils.openUri(this, AppConfig.v2rayNGIssues)
@@ -549,10 +564,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// startActivity<InappBuyActivity>()
}
R.id.logcat -> {
startActivity(Intent(this, LogcatActivity::class.java))
startActivity<LogcatActivity>()
}
}
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
}
@@ -1,31 +1,24 @@
package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.item_qrcode.view.*
import kotlinx.android.synthetic.main.item_recycler_main.view.*
import org.jetbrains.anko.*
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
import com.v2ray.ang.extension.defaultDPreference
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
, ItemTouchHelperAdapter {
@@ -35,112 +28,140 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
private var mActivity: MainActivity = activity
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 lateinit var configs: AngConfig
private val share_method: Array<out String> by lazy {
mActivity.resources.getStringArray(R.array.share_method)
}
var isRunning = false
override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
var changeable: Boolean = true
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) {
if (holder is MainViewHolder) {
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
val config = mActivity.mainViewModel.serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?: return
val outbound = config.getProxyOutbound()
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
val configType = configs.vmess[position].configType
val remarks = configs.vmess[position].remarks
val subid = configs.vmess[position].subid
val address = configs.vmess[position].address
val port = configs.vmess[position].port
val test_result = configs.vmess[position].testResult
holder.name.text = config.remarks
holder.radio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = aff?.getTestDelayString() ?: ""
if (aff?.testDelayMillis?:0L < 0L) {
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
holder.name.text = remarks
holder.radio.isChecked = (position == configs.index)
holder.itemView.backgroundColor = Color.TRANSPARENT
holder.test_result.text = test_result
if (TextUtils.isEmpty(subid)) {
holder.subid.text = ""
} else {
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
}
holder.subscription.text = ""
val json = subStorage?.decodeString(config.subscriptionId)
if (!json.isNullOrBlank()) {
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
holder.subscription.text = sub.remarks
holder.subid.text = "S"
}
var shareOptions = share_method.asList()
if (config.configType == EConfigType.CUSTOM) {
if (configType == AppConfig.EConfigType.Vmess) {
holder.type.text = "vmess"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Custom) {
holder.type.text = mActivity.getString(R.string.server_customize_config)
shareOptions = shareOptions.takeLast(1)
} else if (config.configType == EConfigType.VLESS) {
holder.type.text = config.configType.name
} else {
holder.type.text = config.configType.name.toLowerCase()
holder.statistics.text = ""//mActivity.getString(R.string.server_customize_config)
holder.layout_share.visibility = View.INVISIBLE
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
holder.type.text = "shadowsocks"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Socks) {
holder.type.text = "socks"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
}
holder.statistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
holder.layout_share.setOnClickListener {
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
mActivity.selector(null, share_method.asList()) { dialogInterface, i ->
try {
when (i) {
0 -> {
if (config.configType == EConfigType.CUSTOM) {
shareFullContent(guid)
} else {
val iv = LayoutInflater.from(mActivity).inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
AlertDialog.Builder(mActivity).setView(iv).show()
}
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
mActivity.alert {
customView {
linearLayout {
addView(iv)
}
}
}.show()
}
1 -> {
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
if (AngConfigManager.share2Clipboard(position) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
2 -> shareFullContent(guid)
else -> mActivity.toast("else")
2 -> {
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
else ->
mActivity.toast("else")
}
} catch (e: Exception) {
e.printStackTrace()
}
}.show()
}
}
holder.layout_edit.setOnClickListener {
val intent = Intent().putExtra("guid", guid)
.putExtra("isRunning", isRunning)
if (config.configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
} else {
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
if (configType == AppConfig.EConfigType.Vmess) {
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Custom) {
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Socks) {
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
}
}
holder.layout_remove.setOnClickListener {
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
if (configs.index != position) {
if (AngConfigManager.removeServer(position) == 0) {
notifyItemRemoved(position)
updateSelectedItem(position)
}
}
}
holder.infoContainer.setOnClickListener {
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
if (guid != selected) {
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
if (isRunning) {
mActivity.showCircle()
Utils.stopVService(mActivity)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
V2RayServiceManager.startV2Ray(mActivity)
if (changeable) {
AngConfigManager.setActiveServer(position)
} else {
mActivity.showCircle()
Utils.stopVService(mActivity)
AngConfigManager.setActiveServer(position)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
mActivity.showCircle()
if (!Utils.startVService(mActivity)) {
mActivity.hideCircle()
}
}
}
}
notifyDataSetChanged()
}
}
if (holder is FooterViewHolder) {
@@ -149,43 +170,49 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.layout_edit.visibility = View.INVISIBLE
} else {
holder.layout_edit.setOnClickListener {
Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
Utils.openUri(mActivity, AppConfig.promotionUrl)
}
}
}
}
private fun shareFullContent(guid: String) {
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return when (viewType) {
when (viewType) {
VIEW_TYPE_ITEM ->
MainViewHolder(LayoutInflater.from(parent.context)
return MainViewHolder(parent.context.layoutInflater
.inflate(R.layout.item_recycler_main, parent, false))
else ->
FooterViewHolder(LayoutInflater.from(parent.context)
return FooterViewHolder(parent.context.layoutInflater
.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 {
return if (position == mActivity.mainViewModel.serverList.size) {
VIEW_TYPE_FOOTER
if (position == configs.vmess.count()) {
return VIEW_TYPE_FOOTER
} else {
VIEW_TYPE_ITEM
return VIEW_TYPE_ITEM
}
}
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
val subscription = itemView.tv_subscription!!
val subid = itemView.tv_subid
val radio = itemView.btn_radio!!
val name = itemView.tv_name!!
val test_result = itemView.tv_test_result!!
@@ -218,26 +245,24 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
override fun onItemDismiss(position: Int) {
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
if (configs.index != position) {
// mActivity.alert(R.string.del_config_comfirm) {
// positiveButton(android.R.string.ok) {
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
if (AngConfigManager.removeServer(position) == 0) {
notifyItemRemoved(position)
}
// }
// show()
// }
}
updateSelectedItem(position)
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
AngConfigManager.swapServer(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
//notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
//notifyDataSetChanged()
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
return true
}
override fun onItemMoveCompleted() {
// do nothing
}
}
@@ -4,9 +4,6 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.os.Bundle
import android.support.v7.preference.PreferenceManager
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.text.TextUtils
import android.util.Log
@@ -15,7 +12,9 @@ import android.view.MenuItem
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import com.dinuscxj.itemdecoration.LinearDividerItemDecoration
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.AppManagerUtil
import kotlinx.android.synthetic.main.activity_bypass_list.*
import rx.android.schedulers.AndroidSchedulers
@@ -26,19 +25,21 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.toast
import org.jetbrains.anko.uiThread
import java.net.URL
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 appsAll: List<AppInfo>? = null
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -46,10 +47,11 @@ class PerAppProxyActivity : BaseActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
val dividerItemDecoration = LinearDividerItemDecoration(
this, LinearDividerItemDecoration.LINEAR_DIVIDER_VERTICAL)
recycler_view.addItemDecoration(dividerItemDecoration)
val blacklist = defaultSharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, null)
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io())
@@ -138,15 +140,15 @@ class PerAppProxyActivity : BaseActivity() {
}
})
switch_per_app_proxy.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked)
}
switch_per_app_proxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)
switch_bypass_apps.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked)
}
switch_bypass_apps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false)
et_search.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
@@ -180,7 +182,7 @@ class PerAppProxyActivity : BaseActivity() {
override fun onPause() {
super.onPause()
adapter?.let {
defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply()
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist)
}
}
@@ -218,14 +220,9 @@ class PerAppProxyActivity : BaseActivity() {
private fun selectProxyApp() {
toast(R.string.msg_downloading_content)
val url = AppConfig.androidpackagenamelistUrl
GlobalScope.launch(Dispatchers.IO) {
val content = try {
URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
doAsync {
val content = URL(url).readText()
uiThread {
Log.d("selectProxyApp", content)
selectProxyApp(content)
toast(R.string.toast_success)
@@ -279,4 +276,4 @@ class PerAppProxyActivity : BaseActivity() {
}
return true
}
}
}
@@ -2,12 +2,14 @@ package com.v2ray.ang.ui
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.v2ray.ang.R
import com.v2ray.ang.dto.AppInfo
import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.*
import org.jetbrains.anko.image
import org.jetbrains.anko.layoutInflater
import org.jetbrains.anko.textColor
import java.util.*
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
@@ -43,7 +45,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
// .inflate(R.layout.item_recycler_bypass_list, parent, false))
else -> AppViewHolder(LayoutInflater.from(ctx)
else -> AppViewHolder(ctx.layoutInflater
.inflate(R.layout.item_recycler_bypass_list, parent, false))
}
@@ -66,17 +68,17 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
fun bind(appInfo: AppInfo) {
this.appInfo = appInfo
icon.setImageDrawable(appInfo.appIcon)
icon.image = appInfo.appIcon
// name.text = appInfo.appName
checkBox.isChecked = inBlacklist
package_name.text = appInfo.packageName
if (appInfo.isSystemApp) {
name.text = String.format("** %1s", appInfo.appName)
name.setTextColor(Color.RED)
name.textColor = Color.RED
} else {
name.text = appInfo.appName
name.setTextColor(Color.DKGRAY)
name.textColor = Color.DKGRAY
}
itemView.setOnClickListener(this)
@@ -92,4 +94,4 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
}
}
}
}
}
@@ -1,24 +1,30 @@
package com.v2ray.ang.ui
import android.Manifest
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.preference.PreferenceManager
import android.text.TextUtils
import android.util.Log
import android.view.*
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.fragment_routing_settings.*
import org.jetbrains.anko.toast
import android.view.MenuInflater
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.extension.toast
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.startActivityForResult
import org.jetbrains.anko.support.v4.startActivityForResult
import org.jetbrains.anko.support.v4.toast
import org.jetbrains.anko.uiThread
import java.net.URL
class RoutingSettingsFragment : Fragment() {
companion object {
private const val routing_arg = "routing_arg"
@@ -26,8 +32,6 @@ class RoutingSettingsFragment : Fragment() {
private const val REQUEST_SCAN_APPEND = 12
}
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
@@ -42,10 +46,10 @@ class RoutingSettingsFragment : Fragment() {
return fragment
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val content = defaultSharedPreferences.getString(arguments!!.getString(routing_arg), "")
val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "")
et_routing_content.text = Utils.getEditable(content!!)
setHasOptionsMenu(true)
@@ -59,7 +63,7 @@ class RoutingSettingsFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.save_routing -> {
val content = et_routing_content.text.toString()
defaultSharedPreferences.edit().putString(arguments!!.getString(routing_arg), content).apply()
activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content)
activity?.toast(R.string.toast_success)
true
}
@@ -92,7 +96,7 @@ class RoutingSettingsFragment : Fragment() {
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult(Intent(activity, ScannerActivity::class.java), requestCode)
startActivityForResult<ScannerActivity>(requestCode)
else
activity?.toast(R.string.toast_permission_denied)
}
@@ -114,17 +118,12 @@ class RoutingSettingsFragment : Fragment() {
}
}
activity?.toast(R.string.msg_downloading_content)
GlobalScope.launch(Dispatchers.IO) {
val content = try {
URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
et_routing_content.text = Utils.getEditable(content)
activity?.toast(R.string.toast_success)
toast(R.string.msg_downloading_content)
doAsync {
val content = URL(url).readText()
uiThread {
et_routing_content.text = Utils.getEditable(content!!)
toast(R.string.toast_success)
}
}
return true
@@ -6,7 +6,7 @@ import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import android.os.Bundle
import com.v2ray.ang.extension.toast
import org.jetbrains.anko.*
class ScScannerActivity : BaseActivity() {
companion object {
@@ -24,7 +24,7 @@ class ScScannerActivity : BaseActivity() {
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
startActivityForResult<ScannerActivity>(requestCode)
else
toast(R.string.toast_permission_denied)
}
@@ -43,10 +43,10 @@ class ScScannerActivity : BaseActivity() {
} else {
toast(R.string.toast_failure)
}
startActivity(Intent(this, MainActivity::class.java))
startActivity<MainActivity>()
}
}
finish()
}
}
}
@@ -1,22 +1,104 @@
package com.v2ray.ang.ui
import android.content.*
import android.net.VpnService
import com.v2ray.ang.R
import com.v2ray.ang.util.Utils
import android.os.Bundle
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import java.lang.ref.SoftReference
import android.content.IntentFilter
import kotlinx.android.synthetic.main.activity_main.*
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
class ScSwitchActivity : BaseActivity() {
companion object {
private const val REQUEST_CODE_VPN_PREPARE = 0
}
var isRunning = false
set(value) {
field = value
if (value) {
Utils.stopVService(this)
} else {
val intent = VpnService.prepare(this)
if (intent == null) {
Utils.startVService(this)
} else {
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
}
}
finishActivity()
}
fun finishActivity() {
try {
Observable.timer(5000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
finish()
}
} catch (e: Exception) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
moveTaskToBack(true)
setContentView(R.layout.activity_none)
if (V2RayServiceManager.v2rayPoint.isRunning) {
Utils.stopVService(this)
val isRunning = Utils.isServiceRun(this, "com.v2ray.ang.service.V2RayVpnService")
if (isRunning) {
//Utils.stopVService(this)
mMsgReceive = ReceiveMessageHandler(this@ScSwitchActivity)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
} else {
Utils.startVServiceFromToggle(this)
Utils.startVService(this)
finishActivity()
}
finish()
}
}
override fun onStop() {
super.onStop()
if (mMsgReceive != null) {
unregisterReceiver(mMsgReceive)
mMsgReceive = null
}
}
private var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(activity: ScSwitchActivity) : BroadcastReceiver() {
internal var mReference: SoftReference<ScSwitchActivity> = SoftReference(activity)
override fun onReceive(ctx: Context?, intent: Intent?) {
val activity = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
activity?.isRunning = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
activity?.isRunning = false
}
// AppConfig.MSG_STATE_START_SUCCESS -> {
// activity?.toast(R.string.toast_services_success)
// activity?.isRunning = true
// }
// AppConfig.MSG_STATE_START_FAILURE -> {
// activity?.toast(R.string.toast_services_failure)
// activity?.isRunning = false
// }
// AppConfig.MSG_STATE_STOP_SUCCESS -> {
// activity?.isRunning = false
// }
}
}
}
}
@@ -7,13 +7,23 @@ import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
import android.content.Intent
import android.graphics.BitmapFactory
import android.icu.util.TimeUnit
import android.view.Menu
import android.view.MenuItem
import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.QRCodeDecoder
import org.jetbrains.anko.toast
import rx.Observable
import android.os.SystemClock
import kotlinx.android.synthetic.main.activity_main.*
import rx.Observer
import rx.android.schedulers.AndroidSchedulers
import javax.xml.datatype.DatatypeConstants.SECONDS
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
companion object {
@@ -23,8 +33,8 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
private var mScannerView: ZXingScannerView? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
public override fun onCreate(state: Bundle?) {
super.onCreate(state)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true)
@@ -109,10 +119,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_FILE_CHOOSER -> {
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
REQUEST_FILE_CHOOSER ->
if (resultCode == RESULT_OK) {
try {
val uri = data!!.data
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text)
@@ -121,7 +131,6 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
toast(e.message.toString())
}
}
}
}
}
}
@@ -0,0 +1,139 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
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.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server2.*
import org.jetbrains.anko.*
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 = 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
}
if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
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) {
alert(R.string.del_config_comfirm) {
positiveButton(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)
}
}
@@ -0,0 +1,175 @@
package com.v2ray.ang.ui
import android.os.Bundle
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.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server3.*
import org.jetbrains.anko.*
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) {
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) {
alert(R.string.del_config_comfirm) {
positiveButton(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)
}
}
@@ -0,0 +1,159 @@
package com.v2ray.ang.ui
import android.os.Bundle
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.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server4.*
import org.jetbrains.anko.*
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) {
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) {
alert(R.string.del_config_comfirm) {
positiveButton(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)
}
}
@@ -1,86 +1,54 @@
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.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.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.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.ID_MAIN
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
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_network
import kotlinx.android.synthetic.main.activity_server_vmess.sp_stream_security
import kotlinx.android.synthetic.main.activity_server.*
import org.jetbrains.anko.*
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 {
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 {
resources.getStringArray(R.array.networks)
}
private val headertypes: Array<out String> by lazy {
resources.getStringArray(R.array.headertypes)
}
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)
private val streamsecuritys: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecuritys)
}
override fun onCreate(savedInstanceState: Bundle?) {
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)
val config = MmkvManager.decodeServerConfig(editGuid)
when(config?.configType ?: createConfigType) {
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)
}
if (config != null) {
bindingServer(config)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
@@ -90,49 +58,33 @@ class ServerActivity : BaseActivity() {
/**
* bingding seleced server config
*/
private fun bindingServer(config: ServerConfig): Boolean {
val outbound = config.getProxyOutbound() ?: return false
val streamSetting = config.outboundBean?.streamSettings ?: return false
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
et_remarks.text = Utils.getEditable(config.remarks)
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
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())
et_address.text = Utils.getEditable(vmess.address)
et_port.text = Utils.getEditable(vmess.port.toString())
et_id.text = Utils.getEditable(vmess.id)
et_alterId.text = Utils.getEditable(vmess.alterId.toString())
val security = Utils.arrayFind(securitys, vmess.security)
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 streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
val headerType = Utils.arrayFind(headertypes, vmess.headerType)
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) {
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)
}
outbound.getTransportSettingDetails()?.let { transportDetails ->
sp_header_type.setSelection(Utils.arrayFind(headertypes, transportDetails[0]))
et_request_host.text = Utils.getEditable(transportDetails[1])
et_path.text = Utils.getEditable(transportDetails[2])
sp_stream_security.setSelection(streamSecurity)
}
return true
}
@@ -140,163 +92,113 @@ class ServerActivity : BaseActivity() {
/**
* clear or init server config
*/
private fun clearServer(): Boolean {
fun clearServer(): Boolean {
et_remarks.text = null
et_address.text = null
et_port.text = Utils.getEditable(DEFAULT_PORT.toString())
et_port.text = Utils.getEditable("10086")
et_id.text = null
et_alterId?.text = Utils.getEditable("0")
sp_security?.setSelection(0)
sp_network?.setSelection(0)
et_alterId.text = Utils.getEditable("64")
sp_security.setSelection(0)
sp_network.setSelection(0)
sp_header_type?.setSelection(0)
et_request_host?.text = null
et_path?.text = null
sp_stream_security?.setSelection(0)
sp_allow_insecure?.setSelection(0)
//et_security.text = null
//sp_flow?.setSelection(0)
sp_header_type.setSelection(0)
et_request_host.text = null
et_path.text = null
sp_stream_security.setSelection(0)
return true
}
/**
* save server config
*/
private fun saveServer(): Boolean {
if (TextUtils.isEmpty(et_remarks.text.toString())) {
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.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)
return false
}
if (TextUtils.isEmpty(et_address.text.toString())) {
if (TextUtils.isEmpty(vmess.address)) {
toast(R.string.server_lab_address)
return false
}
val port = Utils.parseInt(et_port.text.toString())
if (port <= 0) {
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
toast(R.string.server_lab_port)
return false
}
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
if (TextUtils.isEmpty(vmess.id)) {
toast(R.string.server_lab_id)
return false
}
et_alterId?.let {
val alterId = Utils.parseInt(et_alterId.text.toString())
if (alterId < 0) {
toast(R.string.server_lab_alterid)
return false
}
if (TextUtils.isEmpty(vmess.alterId.toString()) || vmess.alterId < 0) {
toast(R.string.server_lab_alterid)
return false
}
config.remarks = et_remarks.text.toString().trim()
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
saveVnext(vnext, port, config)
}
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 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(
if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK,
if (sp_header_type != null) headertypes[sp_header_type.selectedItemPosition] else "",
requestHost,
path,
path,
requestHost,
path
)
val allowInsecure = if (sp_allow_insecure == null || allowinsecures[sp_allow_insecure.selectedItemPosition].isBlank()) {
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
if (AngConfigManager.addServer(vmess, edit_index) == 0) {
toast(R.string.toast_success)
finish()
return true
} else {
allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean()
toast(R.string.toast_failure)
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
)
}
/**
* 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)
fun deleteServer(): Boolean {
if (edit_index >= 0) {
alert(R.string.del_config_comfirm) {
positiveButton(android.R.string.ok) {
if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
}
.show()
}
show()
}
} else {
}
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)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (editGuid.isNotEmpty()) {
if (edit_index >= 0) {
if (isRunning) {
delButton?.isVisible = false
saveButton?.isVisible = false
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
delButton?.isVisible = false
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
@@ -313,4 +215,4 @@ class ServerActivity : BaseActivity() {
}
else -> super.onOptionsItemSelected(item)
}
}
}
@@ -1,139 +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 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)
}
}
@@ -1,21 +1,52 @@
package com.v2ray.ang.ui
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.support.v7.preference.*
import android.text.TextUtils
import android.view.View
import android.preference.CheckBoxPreference
import android.preference.EditTextPreference
import android.preference.Preference
import android.preference.PreferenceFragment
import com.v2ray.ang.BuildConfig
//import com.v2ray.ang.InappBuyActivity
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.onClick
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel
import org.jetbrains.anko.act
import org.jetbrains.anko.defaultSharedPreferences
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.toast
import libv2ray.Libv2ray
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_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) }
// 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_VERSION = "pref_version"
// const val PREF_AUTO_RESTART = "pref_auto_restart"
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -24,146 +55,34 @@ class SettingsActivity : BaseActivity() {
title = getString(R.string.title_settings)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
settingsViewModel.startListenPreferenceChange()
}
class SettingsFragment : PreferenceFragmentCompat() {
private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
private val localDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) }
private val fakeDns by lazy { findPreference(AppConfig.PREF_FAKE_DNS_ENABLED) }
private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) }
private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) }
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(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
private val routingCustom: Preference by lazy { findPreference(AppConfig.PREF_ROUTING_CUSTOM) }
val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) }
// val donate: Preference by lazy { findPreference(PREF_DONATE) }
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
val version: Preference by lazy { findPreference(PREF_VERSION) }
private val mode by lazy { findPreference(AppConfig.PREF_MODE) as ListPreference }
private fun restartProxy() {
Utils.stopVService(requireContext())
V2RayServiceManager.startV2Ray(requireContext())
}
private fun isRunning(): Boolean {
return false //TODO no point of adding logic now since Settings will be changed soon
}
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_settings)
perAppProxy.setOnPreferenceClickListener {
if (isRunning()) {
Utils.stopVService(requireContext())
}
startActivity(Intent(activity, PerAppProxyActivity::class.java))
perAppProxy.isChecked = true
true
routingCustom.onClick {
startActivity<RoutingSettingsActivity>()
}
sppedEnabled.setOnPreferenceClickListener {
if (isRunning())
restartProxy()
true
}
sniffingEnabled.setOnPreferenceClickListener {
if (isRunning())
restartProxy()
true
}
proxySharing.setOnPreferenceClickListener {
if (proxySharing.isChecked)
activity?.toast(R.string.toast_warning_pref_proxysharing)
if (isRunning())
restartProxy()
true
}
domainStrategy.setOnPreferenceChangeListener { _, _ ->
if (isRunning())
restartProxy()
true
}
routingMode.setOnPreferenceChangeListener { _, _ ->
if (isRunning())
restartProxy()
true
}
routingCustom.setOnPreferenceClickListener {
if (isRunning())
Utils.stopVService(requireContext())
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
false
}
forwardIpv6.setOnPreferenceClickListener {
if (isRunning())
restartProxy()
true
}
enableLocalDns.setOnPreferenceClickListener {
if (isRunning())
restartProxy()
true
}
domesticDns.setOnPreferenceChangeListener { _, any ->
// domesticDns.summary = any as String
val nval = any as String
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
if (isRunning())
restartProxy()
true
}
remoteDns.setOnPreferenceChangeListener { _, any ->
// remoteDns.summary = any as String
val nval = any as String
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
if (isRunning())
restartProxy()
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 ->
updateMode(newValue.toString())
true
}
mode.dialogLayoutResource = R.layout.preference_with_help_link
// donate.onClick {
// startActivity<InappBuyActivity>()
@@ -191,7 +110,24 @@ class SettingsActivity : BaseActivity() {
// }
// }
perAppProxy.setOnPreferenceClickListener {
startActivity<PerAppProxyActivity>()
perAppProxy.isChecked = true
false
}
remoteDns.setOnPreferenceChangeListener { preference, any ->
// remoteDns.summary = any as String
val nval = any as String
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
true
}
domesticDns.setOnPreferenceChangeListener { preference, any ->
// domesticDns.summary = any as String
val nval = any as String
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
true
}
// socksPort.setOnPreferenceChangeListener { preference, any ->
// socksPort.summary = any as String
// true
@@ -200,51 +136,45 @@ class SettingsActivity : BaseActivity() {
// httpPort.summary = any as String
// true
// }
version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
}
override fun onStart() {
super.onStart()
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
domesticDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
if (TextUtils.isEmpty(remoteDnsString)) {
remoteDnsString = AppConfig.DNS_AGENT
perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false)
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "")
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "")
if (remoteDns.summary == "") {
remoteDns.summary = AppConfig.DNS_AGENT
}
if ( domesticDns.summary == "") {
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")
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
private fun updateMode(mode: String?) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
val vpn = mode == "VPN"
perAppProxy.isEnabled = vpn
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn
fakeDns?.isEnabled = vpn
localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn
if (vpn) {
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false))
override fun onStop() {
super.onStop()
defaultSharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
when (key) {
// PREF_AUTO_RESTART ->
// act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
PREF_PER_APP_PROXY ->
act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
}
}
private fun updateLocalDns(enabled: Boolean) {
fakeDns?.isEnabled = enabled
localDnsPort?.isEnabled = enabled
vpnDns?.isEnabled = !enabled
}
}
fun onModeHelpClicked(view: View) {
Utils.openUri(this, AppConfig.v2rayNGWikiMode)
}
}
}
@@ -1,35 +1,36 @@
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.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_sub_edit.*
import org.jetbrains.anko.*
class SubEditActivity : BaseActivity() {
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val editSubId by lazy { intent.getStringExtra("subId").orEmpty() }
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub_edit)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
title = getString(R.string.title_sub_setting)
val json = subStorage?.decodeString(editSubId)
if (!json.isNullOrBlank()) {
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
if (edit_index >= 0) {
bindingServer(configs.subItem[edit_index])
} else {
clearServer()
}
@@ -39,7 +40,7 @@ class SubEditActivity : BaseActivity() {
/**
* bingding seleced server config
*/
private fun bindingServer(subItem: SubscriptionItem): Boolean {
fun bindingServer(subItem: AngConfig.SubItemBean): Boolean {
et_remarks.text = Utils.getEditable(subItem.remarks)
et_url.text = Utils.getEditable(subItem.url)
@@ -49,7 +50,7 @@ class SubEditActivity : BaseActivity() {
/**
* clear or init server config
*/
private fun clearServer(): Boolean {
fun clearServer(): Boolean {
et_remarks.text = null
et_url.text = null
@@ -59,15 +60,12 @@ class SubEditActivity : BaseActivity() {
/**
* save server config
*/
private fun saveServer(): Boolean {
val subItem: SubscriptionItem
val json = subStorage?.decodeString(editSubId)
var subId = editSubId
if (!json.isNullOrBlank()) {
subItem = Gson().fromJson(json, SubscriptionItem::class.java)
fun saveServer(): Boolean {
val subItem: AngConfig.SubItemBean
if (edit_index >= 0) {
subItem = configs.subItem[edit_index]
} else {
subId = Utils.getUuid()
subItem = SubscriptionItem()
subItem = AngConfig.SubItemBean()
}
subItem.remarks = et_remarks.text.toString()
@@ -82,23 +80,33 @@ class SubEditActivity : BaseActivity() {
return false
}
subStorage?.encode(subId, Gson().toJson(subItem))
toast(R.string.toast_success)
finish()
return true
if (AngConfigManager.addSubItem(subItem, edit_index) == 0) {
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
/**
* save server config
*/
private fun deleteServer(): Boolean {
if (editSubId.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeSubscription(editSubId)
fun deleteServer(): Boolean {
if (edit_index >= 0) {
alert(R.string.del_config_comfirm) {
positiveButton(android.R.string.ok) {
if (AngConfigManager.removeSubItem(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
}
.show()
}
show()
}
} else {
}
return true
}
@@ -108,7 +116,8 @@ class SubEditActivity : BaseActivity() {
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (editSubId.isEmpty()) {
if (edit_index >= 0) {
} else {
del_config?.isVisible = false
}
@@ -126,4 +135,4 @@ class SubEditActivity : BaseActivity() {
}
else -> super.onOptionsItemSelected(item)
}
}
}
@@ -1,18 +1,15 @@
package com.v2ray.ang.ui
import android.content.Intent
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.v2ray.ang.R
import kotlinx.android.synthetic.main.activity_sub_setting.*
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.util.MmkvManager
import org.jetbrains.anko.startActivity
class SubSettingActivity : BaseActivity() {
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) {
@@ -30,8 +27,7 @@ class SubSettingActivity : BaseActivity() {
override fun onResume() {
super.onResume()
subscriptions = MmkvManager.decodeSubscriptions()
adapter.notifyDataSetChanged()
adapter.updateConfigList()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
@@ -44,9 +40,12 @@ class SubSettingActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.add_config -> {
startActivity(Intent(this, SubEditActivity::class.java))
startActivity<SubEditActivity>("position" to -1)
adapter.updateConfigList()
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
@@ -1,41 +1,56 @@
package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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 org.jetbrains.anko.*
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
private var mActivity: SubSettingActivity = activity
private lateinit var configs: AngConfig
override fun getItemCount() = mActivity.subscriptions.size
init {
updateConfigList()
}
override fun getItemCount() = configs.subItem.count()
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) {
val subId = mActivity.subscriptions[position].first
val subItem = mActivity.subscriptions[position].second
holder.name.text = subItem.remarks
holder.url.text = subItem.url
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
val remarks = configs.subItem[position].remarks
val url = configs.subItem[position].url
holder.name.text = remarks
holder.url.text = url
holder.itemView.backgroundColor = Color.TRANSPARENT
holder.layout_edit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
.putExtra("subId", subId)
)
mActivity.startActivity<SubEditActivity>("position" to position)
}
} else {
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return MainViewHolder(LayoutInflater.from(parent.context)
return MainViewHolder(parent.context.layoutInflater
.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)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {
@@ -7,23 +7,21 @@ import android.widget.ArrayAdapter
import android.widget.ListView
import java.util.ArrayList
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import android.content.Intent
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MmkvManager
import kotlinx.android.synthetic.main.activity_tasker.*
class TaskerActivity : BaseActivity() {
private var listview: ListView? = null
private var lstData: 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?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tasker)
@@ -32,11 +30,9 @@ class TaskerActivity : BaseActivity() {
lstData.add("Default")
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
serverStorage?.allKeys()?.forEach { key ->
MmkvManager.decodeServerConfig(key)?.let { config ->
lstData.add(config.remarks)
lstGuid.add(key)
}
AngConfigManager.configs.vmess.forEach {
lstData.add(it.remarks)
lstGuid.add(it.guid)
}
val adapter = ArrayAdapter(this,
android.R.layout.simple_list_item_single_choice, lstData)
@@ -79,10 +75,12 @@ class TaskerActivity : BaseActivity() {
val intent = Intent()
val remarks = lstData[position]
val blurb = if (switch_start_service.isChecked) {
"Start $remarks"
var blurb = ""
if (switch_start_service.isChecked) {
blurb = "Start $remarks"
} else {
"Stop $remarks"
blurb = "Stop $remarks"
}
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
@@ -110,3 +108,4 @@ class TaskerActivity : BaseActivity() {
}
}
File diff suppressed because it is too large Load Diff
@@ -16,7 +16,7 @@ object AppManagerUtil {
val apps = ArrayList<AppInfo>()
for (pkg in packages) {
if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
if (!pkg.hasInternetPermission) continue
val applicationInfo = pkg.applicationInfo
@@ -31,7 +31,7 @@ object AppManagerUtil {
return apps
}
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create {
it.onNext(loadNetworkAppList(ctx))
}
@@ -40,4 +40,4 @@ object AppManagerUtil {
val permissions = requestedPermissions
return permissions?.any { it == Manifest.permission.INTERNET } ?: false
}
}
}
@@ -1,148 +0,0 @@
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)
}
}
@@ -11,30 +11,42 @@ import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.EncodeHintType
import java.util.*
import kotlin.collections.HashMap
import android.app.ActivityManager
import android.content.ClipData
import android.content.Intent
import android.content.res.AssetManager
import android.net.Uri
import android.os.SystemClock
import android.text.TextUtils
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.util.Patterns
import android.view.View
import android.webkit.URLUtil
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.responseLength
import com.v2ray.ang.extension.toast
import com.v2ray.ang.service.V2RayServiceManager
import kotlinx.coroutines.isActive
import com.v2ray.ang.service.V2RayVpnService
import com.v2ray.ang.ui.SettingsActivity
import kotlinx.android.synthetic.main.activity_logcat.*
import me.dozen.dpreference.DPreference
import org.jetbrains.anko.toast
import org.jetbrains.anko.uiThread
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.net.*
import kotlin.coroutines.coroutineContext
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.math.BigInteger
import java.util.concurrent.TimeUnit
import libv2ray.Libv2ray
object Utils {
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
*
@@ -89,7 +101,7 @@ object Utils {
try {
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(null, content)
cmb.setPrimaryClip(clipData)
cmb.primaryClip = clipData
} catch (e: Exception) {
e.printStackTrace()
}
@@ -122,31 +134,41 @@ object Utils {
/**
* get remote dns servers from preference
*/
fun getRemoteDnsServers(): List<String> {
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_AGENT)
fun getRemoteDnsServers(defaultDPreference: DPreference): ArrayList<String> {
val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, AppConfig.DNS_AGENT)
val ret = ArrayList<String>()
if (!TextUtils.isEmpty(remoteDns)) {
remoteDns
.split(",")
.forEach {
if (Utils.isPureIpAddress(it)) {
ret.add(it)
}
}
}
if (ret.size == 0) {
ret.add(AppConfig.DNS_AGENT)
}
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
*/
fun getDomesticDnsServers(): List<String> {
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_DIRECT)
fun getDomesticDnsServers(defaultDPreference: DPreference): ArrayList<String> {
val domesticDns = defaultDPreference.getPrefString(SettingsActivity.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
val ret = ArrayList<String>()
if (!TextUtils.isEmpty(domesticDns)) {
domesticDns
.split(",")
.forEach {
if (Utils.isPureIpAddress(it)) {
ret.add(it)
}
}
}
if (ret.size == 0) {
ret.add(AppConfig.DNS_DIRECT)
}
return ret
}
@@ -247,7 +269,7 @@ object Utils {
*/
fun isValidUrl(value: String?): Boolean {
try {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true
}
} catch (e: WriterException) {
@@ -257,13 +279,71 @@ object Utils {
return false
}
fun startVServiceFromToggle(context: Context): Boolean {
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
context.toast(R.string.app_tile_first_use)
/**
* 判断服务是否后台运行
* @param context
* * Context
* *
* @param className
* * 判断的服务名字
* *
* @return true 在运行 false 不在运行
*/
fun isServiceRun(context: Context, className: String): Boolean {
var isRun = false
val activityManager = context
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val serviceList = activityManager
.getRunningServices(999)
val size = serviceList.size
for (i in 0..size - 1) {
if (serviceList[i].service.className == className) {
isRun = true
break
}
}
return isRun
}
/**
* startVService
*/
fun startVService(context: Context): Boolean {
context.toast(R.string.toast_services_start)
if (AngConfigManager.genStoreV2rayConfig(-1)) {
val configContent = AngConfigManager.currGeneratedV2rayConfig()
val configType = AngConfigManager.currConfigType()
if (configType == AppConfig.EConfigType.Custom) {
try {
Libv2ray.testConfig(configContent)
} catch (e: Exception) {
context.toast(e.toString())
return false
}
}
V2RayVpnService.startV2Ray(context)
return true
} else {
return false
}
V2RayServiceManager.startV2Ray(context)
return true
}
/**
* startVService
*/
fun startVService(context: Context, guid: String): Boolean {
val index = AngConfigManager.getIndexViaGuid(guid)
return startVService(context, index)
}
/**
* startVService
*/
fun startVService(context: Context, index: Int): Boolean {
AngConfigManager.setActiveServer(index)
return startVService(context)
}
/**
@@ -362,11 +442,12 @@ object Utils {
return path
}
/**
* readTextFromAssets
*/
fun readTextFromAssets(context: Context, fileName: String): String {
val content = context.assets.open(fileName).bufferedReader().use {
fun readTextFromAssets(app: AngApplication, fileName: String): String {
val content = app.assets.open(fileName).bufferedReader().use {
it.readText()
}
return content
@@ -380,7 +461,7 @@ object Utils {
val command = "/system/bin/ping -c 3 $url"
val process = Runtime.getRuntime().exec(command)
val allText = process.inputStream.bufferedReader().use { it.readText() }
if (allText.isNotBlank()) {
if (!TextUtils.isEmpty(allText)) {
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
if (temps.count() > 0 && temps[0].length < 10) {
@@ -396,51 +477,33 @@ object Utils {
/**
* tcping
*/
suspend fun tcping(url: String, port: Int): Long {
fun tcping(url: String, port: Int): String {
var time = -1L
for (k in 0 until 2) {
val one = socketConnectTime(url, port)
if (!coroutineContext.isActive) {
break
}
if (one != -1L && (time == -1L || one < time)) {
if (one != -1L )
if(time == -1L || one < time) {
time = one
}
}
return time
return time.toString() + "ms"
}
fun socketConnectTime(url: String, port: Int): Long {
try {
val socket = Socket()
synchronized(this) {
tcpTestingSockets.add(socket)
}
val start = System.currentTimeMillis()
socket.connect(InetSocketAddress(url, port))
val socket = Socket(url, port)
val time = System.currentTimeMillis() - start
synchronized(this) {
tcpTestingSockets.remove(socket)
}
socket.close()
return time
} catch (e: UnknownHostException) {
e.printStackTrace()
} catch (e: IOException) {
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
e.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
return -1
}
fun closeAllTcpSockets() {
synchronized(this) {
tcpTestingSockets.forEach {
it?.close()
}
tcpTestingSockets.clear()
}
}
}
@@ -1,123 +1,149 @@
package com.v2ray.ang.util
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.google.gson.*
import com.tencent.mmkv.MMKV
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AngConfig.VmessBean
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.ui.SettingsActivity
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONArray
import com.google.gson.JsonObject
object V2rayConfigUtil {
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val requestObj: JsonObject by lazy {
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 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)
/**
* 生成v2ray的客户端配置文件
*/
fun getV2rayConfig(context: Context, guid: String): Result {
fun getV2rayConfig(app: AngApplication, vmess: VmessBean): Result {
var result = Result(false, "")
try {
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
if (config.configType == EConfigType.CUSTOM) {
val raw = serverRawStorage?.decodeString(guid)
val customConfig = if (raw.isNullOrBlank()) {
config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
} else {
raw
}
Log.d("V2rayConfigUtilGoLog", customConfig)
return Result(true, customConfig)
//检查设置
// if (config.index < 0
// || config.vmess.count() <= 0
// || config.index > config.vmess.count() - 1
// ) {
// return result
// }
if (vmess.configType == AppConfig.EConfigType.Vmess) {
result = getV2rayConfigType1(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Custom) {
result = getV2rayConfigType2(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Shadowsocks) {
result = getV2rayConfigType1(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Socks) {
result = getV2rayConfigType1(app, vmess)
}
val outbound = config.getProxyOutbound() ?: return Result(false, "")
val result = getV2rayNonCustomConfig(context, outbound)
val domainName = parseDomainName(result.content)
if (!TextUtils.isEmpty(domainName)) {
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
}
Log.d("V2rayConfigUtilGoLog", result.content)
return result
} catch (e: Exception) {
e.printStackTrace()
return Result(false, "")
return result
}
}
/**
* 生成v2ray的客户端配置文件
*/
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result {
private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result {
val result = Result(false, "")
//取得默认配置
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
if (TextUtils.isEmpty(assets)) {
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
}
}
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
/**
* 生成v2ray的客户端配置文件
*/
private fun getV2rayConfigType2(app: AngApplication, vmess: VmessBean): Result {
val result = Result(false, "")
try {
val guid = vmess.guid
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
result.status = true
result.content = jsonConfig
return result
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
inbounds(v2rayConfig)
httpRequestObject(outbound)
v2rayConfig.outbounds[0] = outbound
routing(v2rayConfig)
fakedns(v2rayConfig)
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
customLocalDns(v2rayConfig)
} else {
customRemoteDns(v2rayConfig)
} catch (e: Exception) {
e.printStackTrace()
return result
}
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(v2rayConfig: V2rayConfig): Boolean {
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
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 ->
if (!(settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) ?: false)) {
//bind all inbounds to localhost if the user requests
curInbound.listen = "127.0.0.1"
}
}
v2rayConfig.inbounds[0].port = 10808
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
?: 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")
}
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, ""))
//v2rayConfig.inbounds[1].port = httpPort
// if (httpPort > 0) {
// if (socksPort > 0) {
// v2rayConfig.inbounds[0].port = socksPort
// }
// if (lanconnPort > 0) {
// val httpCopy = v2rayConfig.inbounds[0].copy()
// httpCopy.port = httpPort
// httpCopy.port = lanconnPort
// httpCopy.protocol = "http"
// v2rayConfig.inbounds.add(httpCopy)
// }
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SNIFFING_ENABLED, true)
} catch (e: Exception) {
e.printStackTrace()
return false
@@ -125,39 +151,231 @@ object V2rayConfigUtil {
return true
}
private fun fakedns(v2rayConfig: V2rayConfig) {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
v2rayConfig.fakedns = V2rayConfig.FakednsBean()
v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
it.settings?.domainStrategy = "UseIP"
/**
* vmess协议服务器配置
*/
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
try {
val outbound = v2rayConfig.outbounds[0]
when (vmess.configType) {
AppConfig.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)
outbound.protocol = "vmess"
}
AppConfig.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
outbound.protocol = "shadowsocks"
}
AppConfig.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
outbound.protocol = "socks"
}
else -> {
}
}
var serverDomain: String
if(Utils.isIpv6Address(vmess.address)) {
serverDomain = String.format("[%s]:%s", vmess.address, vmess.port)
} else {
serverDomain = String.format("%s:%s", vmess.address, vmess.port)
}
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, serverDomain)
} 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()
wssettings.connectionReuse = true
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.connectionReuse = true
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
*/
private fun routing(v2rayConfig: V2rayConfig): Boolean {
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
try {
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "", AppConfig.TAG_AGENT, 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)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig)
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch"
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
// Hardcode googleapis.cn
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_AGENT,
domain = arrayListOf("domain:googleapis.cn")
type = "field",
outboundTag = AppConfig.TAG_AGENT,
domain = arrayListOf("domain:googleapis.cn")
)
when (routingMode) {
"0" -> {
}
"1" -> {
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
}
@@ -221,19 +439,21 @@ object V2rayConfigUtil {
rulesIP.outboundTag = tag
rulesIP.ip = ArrayList<String>()
userRule.split(",").map { it.trim() }.forEach {
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
rulesIP.ip?.add(it)
} else if (it.isNotEmpty())
userRule.trim().replace("\n", "")
.split(",")
.forEach {
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
rulesIP.ip?.add(it)
} else if (it.isNotBlank() || it.isNotEmpty())
// if (Utils.isValidUrl(it)
// || it.startsWith("geosite:")
// || it.startsWith("regexp:")
// || it.startsWith("domain:")
// || it.startsWith("full:"))
{
rulesDomain.domain?.add(it)
}
}
{
rulesDomain.domain?.add(it)
}
}
if (rulesDomain.domain?.size!! > 0) {
v2rayConfig.routing.rules.add(rulesDomain)
}
@@ -248,8 +468,9 @@ object V2rayConfigUtil {
private fun userRule2Domian(userRule: String): ArrayList<String> {
val domain = ArrayList<String>()
userRule.split(",").map { it.trim() }.forEach {
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
userRule.trim().replace("\n", "").split(",").forEach {
if ((it.startsWith("geosite:") || it.startsWith("domain:")) &&
it.isNotBlank() && it.isNotEmpty()) {
domain.add(it)
}
}
@@ -259,39 +480,33 @@ object V2rayConfigUtil {
/**
* Custom Dns
*/
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
private fun customLocalDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
try {
val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>()
val remoteDns = Utils.getRemoteDnsServers()
val domesticDns = Utils.getDomesticDnsServers()
val geositeCn = arrayListOf("geosite:cn")
val geoipCn = arrayListOf("geoip:cn")
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "")
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "")
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
// fakedns with all domains to make it always top priority
servers.add(V2rayConfig.DnsBean.ServersBean(address = "fakedns", domains = geositeCn.plus(proxyDomain).plus(directDomain)))
}
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference)
remoteDns.forEach {
servers.add(it)
}
if (proxyDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, proxyDomain, null))
}
if (directDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
}
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
if (routingMode == "2" || routingMode == "3") {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference)
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 blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "")
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
if (dirDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain))
}
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
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) {
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
}
@@ -306,57 +521,53 @@ object V2rayConfigUtil {
// DNS inbound对象
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 dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = 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))
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))
V2rayConfig.OutboundBean(
protocol = "dns",
tag = "dns-out",
settings = null,
streamSettings = null,
mux = null))
}
// DNS routing
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)
)
}
if (!remoteDns.first().startsWith("https")) {
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_AGENT,
port = "53",
ip = arrayListOf(remoteDns.first()),
domain = null)
)
}
// DNS routing
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = domesticDns,
domain = null)
)
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_AGENT,
port = "53",
ip = remoteDns,
domain = null)
)
// DNS routing tag
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
inboundTag = arrayListOf("dns-in"),
inboundTag = arrayListOf<String>("dns-in"),
outboundTag = "dns-out",
domain = null)
)
@@ -371,18 +582,15 @@ object V2rayConfigUtil {
/**
* Custom Remote Dns
*/
private fun customRemoteDns(v2rayConfig: V2rayConfig): Boolean {
private fun customRemoteDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
try {
val servers = ArrayList<Any>()
val hosts = mutableMapOf<String, String>()
Utils.getRemoteDnsServers().forEach {
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
servers.add(it)
}
// hardcode googleapi rule to fix play store problems
hosts.put("domain:googleapis.cn", "googleapis.com")
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers, hosts = hosts)
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers)
} catch (e: Exception) {
e.printStackTrace()
return false
@@ -390,26 +598,80 @@ object V2rayConfigUtil {
return true
}
private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean {
/**
* is valid config
*/
fun isValidConfig(conf: String): Boolean {
try {
if (outbound.streamSettings?.network == DEFAULT_NETWORK
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
val Host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
val requestString: String by lazy {
"""{"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"}}"""
}
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(requestString, V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java)
outbound.streamSettings?.tcpSettings?.header?.request?.path = path!!
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = Host!!
}
} catch (e: Exception) {
e.printStackTrace()
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
}
return true
}
}
private fun parseDomainName(jsonConfig: String): String {
try {
val jObj = JSONObject(jsonConfig)
var domainName: String
if (jObj.has("outbound")) {
domainName = parseDomainName(jObj.optJSONObject("outbound"))
if (!TextUtils.isEmpty(domainName)) {
return domainName
}
}
if (jObj.has("outbounds")) {
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
domainName = parseDomainName(jObj.optJSONArray("outbounds").getJSONObject(i))
if (!TextUtils.isEmpty(domainName)) {
return domainName
}
}
}
if (jObj.has("outboundDetour")) {
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
domainName = parseDomainName(jObj.optJSONArray("outboundDetour").getJSONObject(i))
if (!TextUtils.isEmpty(domainName)) {
return domainName
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
private fun parseDomainName(outbound: JSONObject): String {
try {
if (outbound.has("settings")) {
var 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 ""
}
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 String.format("[%s]:%s", address, port)
} else {
return String.format("%s:%s", address, port)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
}
@@ -1,147 +0,0 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.MutableLiveData
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
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.*
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
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 updateListAction by lazy { MutableLiveData<Int>() }
val updateTestResultAction by lazy { MutableLiveData<String>() }
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
fun startListenBroadcast() {
isRunning.value = false
getApplication<AngApplication>().registerReceiver(mMsgReceiver, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onCleared() {
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
Utils.closeAllTcpSockets()
Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared")
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() {
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
Utils.closeAllTcpSockets()
MmkvManager.clearAllTestDelayResults()
updateListAction.value = -1 // update all
getApplication<AngApplication>().toast(R.string.connection_test_testing)
for (guid in serverList) {
serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?.getProxyOutbound()?.let { outbound ->
val serverAddress = outbound.getServerAddress()
val serverPort = outbound.getServerPort()
if (serverAddress != null && serverPort != null) {
tcpingTestScope.launch {
val testResult = Utils.tcping(serverAddress, serverPort)
launch(Dispatchers.Main) {
MmkvManager.encodeServerTestDelayMillis(guid, testResult)
updateListAction.value = serverList.indexOf(guid)
}
}
}
}
}
}
fun testCurrentServerRealPing() {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
GlobalScope.launch(Dispatchers.IO) {
val result = Utils.testConnection(getApplication(), socksPort)
launch(Dispatchers.Main) {
updateTestResultAction.value = result
}
}
}
private val mMsgReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
isRunning.value = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
isRunning.value = false
}
AppConfig.MSG_STATE_START_SUCCESS -> {
getApplication<AngApplication>().toast(R.string.toast_services_success)
isRunning.value = true
}
AppConfig.MSG_STATE_START_FAILURE -> {
getApplication<AngApplication>().toast(R.string.toast_services_failure)
isRunning.value = false
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
isRunning.value = false
}
}
}
}
}
@@ -1,57 +0,0 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.content.SharedPreferences
import android.support.v7.preference.PreferenceManager
import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MmkvManager
class SettingsViewModel(application: Application) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
fun startListenPreferenceChange() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).registerOnSharedPreferenceChangeListener(this)
}
override fun onCleared() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).unregisterOnSharedPreferenceChangeListener(this)
Log.i(AppConfig.ANG_PACKAGE, "Settings ViewModel is cleared")
super.onCleared()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key")
when(key) {
AppConfig.PREF_MODE,
AppConfig.PREF_VPN_DNS,
AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_ROUTING_MODE,
AppConfig.PREF_V2RAY_ROUTING_AGENT,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT, -> {
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
}
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()))
}
}
}
}
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@color/secondary_text" />
<item android:state_focused="true"
android:drawable="@color/secondary_text" />
<item android:state_hovered="true"
android:drawable="@color/secondary_text" />
<item android:drawable="@color/colorPrimary_text" />
</selector>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/accent"/>
<corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#009963"/>
<corners android:radius="20dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

@@ -48,17 +48,14 @@
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:nextFocusRight="@+id/fab" />
android:layout_weight="1" />
<LinearLayout
android:id="@+id/layout_test"
android:layout_width="match_parent"
android:layout_height="@dimen/connection_test_height"
android:background="@drawable/background_test_button"
android:gravity="center|left"
android:clickable="true"
android:focusable="true">
android:background="@color/colorPrimary_text"
android:gravity="center|left">
<TextView
android:id="@+id/tv_test_state"
@@ -95,10 +92,7 @@
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_v_idle"
android:clickable="true"
android:focusable="true"
app:layout_anchorGravity="bottom|right|end"
android:nextFocusLeft="@+id/recycler_view" />
app:layout_anchorGravity="bottom|right|end" />
</com.github.jorgecastilloprz.FABProgressCircle>
</android.support.design.widget.CoordinatorLayout>
@@ -113,23 +107,7 @@
app:headerLayout="@layout/nav_header"
app:itemIconTint="@color/colorPrimary_dark"
app:itemTextColor="@color/colorPrimary"
app:menu="@menu/menu_drawer" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/white"
android:padding="14dp">
<TextView
android:id="@+id/version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/accent" />
</LinearLayout>
</android.support.design.widget.NavigationView>
app:menu="@menu/menu_drawer" />
</android.support.v4.widget.DrawerLayout>
@@ -143,10 +143,15 @@
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" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
@@ -156,15 +161,10 @@
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
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
@@ -241,28 +241,6 @@
</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
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -271,4 +249,4 @@
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
</ScrollView>
@@ -68,15 +68,20 @@
android:text="@string/server_lab_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<EditText
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:text="" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
@@ -80,51 +80,11 @@
</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="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" />
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>
</ScrollView>
</ScrollView>
@@ -2,10 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
android:gravity="center_vertical">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/icon"
@@ -43,7 +40,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:paddingStart="2dp"
android:paddingLeft="2dp"
android:paddingEnd="6dp"
@@ -1,17 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
android:id="@+id/item_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<android.support.v7.widget.CardView
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
app:cardCornerRadius="5dp">
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="horizontal"
card_view:cardCornerRadius="5dp">
<LinearLayout
android:id="@+id/info_container"
@@ -19,20 +21,31 @@
android:layout_height="@dimen/server_height"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
android:nextFocusRight="@+id/layout_share">
android:orientation="horizontal">
<android.support.v7.widget.AppCompatRadioButton
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_subid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<android.support.v7.widget.AppCompatRadioButton
android:id="@+id/btn_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -72,31 +85,20 @@
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="vertical"
android:paddingEnd="5dp">
<TextView
android:id="@+id/tv_test_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingEnd="5dp">
<TextView
android:id="@+id/tv_subscription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorSubscription"
android:textSize="10sp"
tools:text="Sub" />
<TextView
android:id="@+id/tv_test_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorPing"
android:textSize="10sp"
tools:text="214ms" />
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorPing"
android:textSize="10sp" />
</LinearLayout>
<LinearLayout
@@ -109,14 +111,10 @@
<LinearLayout
android:id="@+id/layout_share"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="@dimen/server_height"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/layout_margin_spacing"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:nextFocusLeft="@+id/info_container">
android:paddingEnd="@dimen/layout_margin_right_height">
<ImageView
android:layout_width="wrap_content"
@@ -128,13 +126,10 @@
<LinearLayout
android:id="@+id/layout_edit"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="@dimen/server_height"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/layout_margin_spacing"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
android:paddingEnd="@dimen/layout_margin_right_height">
<ImageView
android:layout_width="@dimen/png_height"
@@ -146,13 +141,10 @@
<LinearLayout
android:id="@+id/layout_remove"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="@dimen/server_height"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/layout_margin_spacing"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
android:paddingEnd="@dimen/layout_margin_right_height">
<ImageView
android:layout_width="@dimen/png_height"
@@ -183,4 +175,4 @@
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</LinearLayout>
@@ -6,7 +6,6 @@
android:gravity="center_vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
@@ -14,8 +13,7 @@
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="horizontal"
card_view:cardCornerRadius="5dp"
android:nextFocusRight="@+id/layout_edit">
card_view:cardCornerRadius="5dp">
<LinearLayout
android:id="@+id/info_container"
@@ -64,14 +62,11 @@
<LinearLayout
android:id="@+id/layout_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="@dimen/server_height"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/layout_margin_spacing"
android:visibility="invisible"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
android:paddingEnd="@dimen/layout_margin_right_height"
android:visibility="invisible">
<ImageView
android:layout_width="@dimen/png_height"
@@ -83,14 +78,10 @@
<LinearLayout
android:id="@+id/layout_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="@dimen/server_height"
android:gravity="center"
android:orientation="vertical"
android:padding="@dimen/layout_margin_spacing"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:nextFocusLeft="@+id/item_cardview">
android:paddingEnd="@dimen/layout_margin_right_height">
<ImageView
android:layout_width="@dimen/png_height"
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="onModeHelpClicked"
android:text="@string/title_mode_help"
android:textAlignment="textStart"
android:textStyle="italic" />
@@ -1,5 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="@+id/layout_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -7,9 +14,16 @@
android:gravity="center"
android:orientation="vertical">
<ImageView
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="10dp"
android:src="@drawable/ic_v" />
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/app_widget_name"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
</LinearLayout>
+15 -12
View File
@@ -4,7 +4,12 @@
tools:showIn="navigation_view">
<group
android:id="@+id/group_main">
android:id="@+id/group_main"
android:checkableBehavior="single">
<item
android:id="@+id/server_profile"
android:icon="@drawable/ic_description_white_24dp"
android:title="@string/title_server" />
<item
android:id="@+id/sub_setting"
android:icon="@drawable/ic_subscriptions_white_24dp"
@@ -16,27 +21,25 @@
</group>
<group android:id="@+id/group_id2">
<item
<item android:title="@string/title_about">
<menu>
<item
android:id="@+id/promotion"
android:icon="@drawable/ic_whatshot_white_24dp"
android:title="@string/title_pref_promotion" />
<item
<item
android:id="@+id/donate"
android:icon="@drawable/ic_attach_money_white_24dp"
android:title="@string/title_pref_donate" />
<item
<item
android:id="@+id/logcat"
android:icon="@drawable/ic_logcat_white_24dp"
android:title="@string/title_logcat" />
<item
<item
android:id="@+id/feedback"
android:icon="@drawable/ic_feedback_white_24dp"
android:title="@string/title_pref_feedback" />
<!-- place holder for version text at the bottom -->
<item
android:id="@+id/placeholder"
android:enabled="false"
android:title="" />
</group>
</menu>
</item>
</menu>
@@ -6,9 +6,4 @@
android:icon="@drawable/ic_copy_white"
android:title="@string/logcat_copy"
app:showAsAction="always" />
<item
android:id="@+id/delete"
android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/logcat_delete"
app:showAsAction="ifRoom" />
</menu>
+36 -1
View File
@@ -17,6 +17,17 @@
<copyright>Copyright(C) 2008-2011 The Android Open Source Project</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>Apache Commons Validator</name>
<url>http://commons.apache.org/proper/commons-collections/</url>
<copyright>Copyright(C) 2001-2014 The Apache Software Foundation</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>anko</name>
<url>https://github.com/Kotlin/anko</url>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>Google Gson</name>
<url>https://github.com/google/gson</url>
@@ -41,9 +52,33 @@
<copyright>Copyright 2015 Square, Inc.</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>ReactiveNetwork</name>
<url>https://github.com/pwittchen/ReactiveNetwork</url>
<copyright>Copyright 2016 Piotr Wittchen</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>RecyclerItemDecoration</name>
<url>https://github.com/dinuscxj/RecyclerItemDecoration</url>
<copyright>Copyright 2015-2019 dinus</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>RxKotlin</name>
<url>https://github.com/ReactiveX/RxKotlin</url>
<copyright>Copyright 2012 Netflix, Inc.</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>RxPermissions</name>
<url>https://github.com/tbruyelle/RxPermissions</url>
<license>Apache Software License 2.0</license>
</notice>
</notices>
<notice>
<name>RecyclerItemDecoration</name>
<url>https://github.com/dinuscxj/RecyclerItemDecoration</url>
<copyright>Copyright 2015-2019 dinus</copyright>
<license>Apache Software License 2.0</license>
</notice>
</notices>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://stackoverflow.com/a/52960668/5495739 -->
<resources xmlns:tools="http://schemas.android.com/tools">
<bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,PrivateResource">false</bool>
<dimen name="preference_category_padding_start" tools:ignore="MissingDefaultResource,PrivateResource">0dp</dimen>
</resources>
@@ -3,11 +3,9 @@
<string name="app_name">v2rayNG</string>
<string name="app_widget_name">开关</string>
<string name="app_tile_name">开关</string>
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
<string name="app_tile_first_use">初次使用此功能请先用APP激活VPN</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">数据迁移成功!</string>
<string name="migration_fail">数据迁移失败啦!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string>
@@ -43,10 +41,10 @@
<string name="server_lab_network">传输协议(network)</string>
<string name="server_lab_more_function">功能设置(不清楚则保持默认值)</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_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/grpc serviceName</string>
<string name="server_lab_stream_security">底层传输安全(tls)</string>
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
<string name="server_lab_request_host">伪装域名host(host/ws host/h2 host)/QUIC 加密方式</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥</string>
<string name="server_lab_stream_security">底层传输安全</string>
<string name="server_lab_allow_insecure">allowInsecure</string>
<string name="server_lab_address3">服务器地址</string>
<string name="server_lab_port3">服务器端口</string>
<string name="server_lab_id3">密码</string>
@@ -75,8 +73,9 @@
<!-- Preferences -->
<string name="title_settings">设置</string>
<string name="title_about">关于</string>
<string name="title_advanced">进阶设置</string>
<string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用代理</string>
<string name="summary_pref_per_app_proxy">分应用代理仅支持 Android 5.0 Lollipop 及更高</string>
@@ -84,16 +83,13 @@
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
<string name="title_pref_speed_enabled">启用速度显示</string>
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
<string name="summary_pref_speed_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="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="summary_pref_local_dns_enabled">DNS由本地设备的v2Ray-core处理(测试)</string>
<string name="title_pref_forward_ipv6">IPv6 路由</string>
<string name="summary_pref_forward_ipv6">转发 IPv6 流量到远程服务器(测试)</string>
@@ -110,9 +106,7 @@
<string name="title_pref_remote_dns">远程DNS (可选)</string>
<string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
<string name="title_pref_domestic_dns">境内DNS (可选,本地DNS模式下生效)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5代理端口</string>
@@ -121,9 +115,6 @@
<string name="title_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="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
<string name="summary_pref_tg_group">加入Telegram Group</string>
@@ -132,8 +123,7 @@
<string name="title_pref_promotion">推广</string>
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">点此查看更多帮助</string>
<string name="title_pref_version">版本</string>
<string name="donate_error_setup">初始化错误:</string>
<string name="donate_error_inventory">无法查询到项目</string>
@@ -145,8 +135,7 @@
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">复制</string>
<string name="logcat_delete">删除</string>
<string name="title_export_all">导出全部(非自定义)配置至剪贴板</string>
<string name="title_export_all">导出全部配置至剪贴板</string>
<string name="title_sub_setting">订阅设置</string>
<string name="sub_setting_remarks">备注</string>
<string name="sub_setting_url">地址(url)</string>
@@ -191,15 +180,5 @@
<item>绕过大陆地址</item>
<item>绕过局域网及大陆地址</item>
</string-array>
<string name="title_pref_proxy_sharing_enabled">代理共享</string>
<string name="summary_pref_proxy_sharing_enabled">绑定代理入口ip到0.0.0.0</string>
<string name="toast_warning_pref_proxysharing">其他设备可以使用socks/http协议通过您的IP地址连接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n仅在受信任的网络中启用以避免未经授权的连接</string>
<string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string>
<string name="toast_malformed_josn">配置格式错误</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>仅代理</item>
</string-array>
</resources>
@@ -3,11 +3,9 @@
<string name="app_name">v2rayNG</string>
<string name="app_widget_name">切換</string>
<string name="app_tile_name">切換</string>
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式來啟用 VPN</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="migration_success">數據遷移成功!</string>
<string name="migration_fail">數據遷移失敗啦!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string>
@@ -19,7 +17,7 @@
<string name="toast_services_failure">啟動服務失敗</string>
<!--ServerActivity-->
<string name="title_server">設定檔</string>
<string name="title_server">組態</string>
<string name="menu_item_add_config">新增組態</string>
<string name="menu_item_save_config">儲存組態</string>
<string name="menu_item_del_config">刪除組態</string>
@@ -43,10 +41,10 @@
<string name="server_lab_network">網路</string>
<string name="server_lab_more_function">更多功能</string>
<string name="server_lab_head_type">標頭類型</string>
<string name="server_lab_request_host">要求主機(host)(host/ws host/h2 host)/QUIC加密方式</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰/kcp seed/grpc serviceName</string>
<string name="server_lab_stream_security">傳輸層安全性(tls)</string>
<string name="server_lab_allow_insecure">跳過證書驗證(allowInsecure)</string>
<string name="server_lab_request_host">要求主機(host/ws host/h2 host)/QUIC加密方式</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰</string>
<string name="server_lab_stream_security">傳輸層安全性</string>
<string name="server_lab_allow_insecure">allowInsecure</string>
<string name="server_lab_address3">伺服器位址</string>
<string name="server_lab_port3">伺服器埠</string>
<string name="server_lab_id3">密碼</string>
@@ -59,7 +57,7 @@
<string name="title_file_chooser">選取一個設定檔</string>
<string name="toast_require_file_manager">請安裝檔案總管。</string>
<string name="server_customize_config">自訂組態</string>
<string name="toast_config_file_invalid">無效設定檔</string>
<string name="toast_config_file_invalid">無效組態</string>
<string name="server_lab_content">內容</string>
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
<string name="toast_invalid_url">網址無效</string>
@@ -76,8 +74,9 @@
<!-- Preferences -->
<string name="title_settings">設定</string>
<string name="title_about">關於</string>
<string name="title_advanced">進階設定</string>
<string name="title_vpn_settings">VPN 設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
<string name="summary_pref_per_app_proxy">Proxy 個別應用程式模式只支援 Android 5.0 (Lollipop) 或更高</string>
@@ -85,16 +84,13 @@
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
<string name="title_pref_speed_enabled">啟用速度顯示</string>
<string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖標顯示流量的路由情況</string>
<string name="summary_pref_speed_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="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="summary_pref_local_dns_enabled">DNS請求由本地的v2ray core處理(測試</string>
<string name="title_pref_forward_ipv6">IPv6 路由</string>
<string name="summary_pref_forward_ipv6">向遠端重新導向 IPv6 流量(測試)</string>
@@ -108,22 +104,18 @@
<string name="summary_pref_donate">向開發人員捐款</string>
<string name="donate_detail">新增一些實驗性進階功能</string>
<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="title_pref_vpn_dns">VPN DNS (僅支持 IPv4/v6)</string>
<string name="title_pref_domestic_dns">境内DNS (可選)</string>
<string name="title_pref_domestic_dns">境内DNS (可选,僅本地DNS模式下生效)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5代理連接埠</string>
<string name="summary_pref_socks_port">SOCKS5代理連接埠</string>
<string name="title_pref_socks_port">SOCKS5代理端口</string>
<string name="summary_pref_socks_port">SOCKS5代理端口</string>
<string name="title_pref_http_port">HTTP代理連接埠</string>
<string name="summary_pref_http_port">HTTP代理連接埠</string>
<string name="title_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="summary_pref_feedback">回饋提升或前往 GitHub 回報 Bug</string>
@@ -133,8 +125,7 @@
<string name="title_pref_promotion">推廣</string>
<string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">點此查看更多幫助</string>
<string name="title_pref_version">版本</string>
<string name="donate_error_setup">錯誤設定:</string>
<string name="donate_error_inventory">Error querying inventory</string>
@@ -146,8 +137,7 @@
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">複製</string>
<string name="logcat_delete">刪除</string>
<string name="title_export_all">匯出全部(非自訂)配置至剪貼簿</string>
<string name="title_export_all">匯出全部配置至剪貼簿</string>
<string name="title_sub_setting">訂閱設定</string>
<string name="sub_setting_remarks">備註</string>
<string name="sub_setting_url">位址(url)</string>
@@ -163,7 +153,7 @@
<string name="routing_settings_delete">清除</string>
<string name="routing_settings_scan_replace">掃描並取代</string>
<string name="routing_settings_scan_append">掃描並附加</string>
<string name="routing_settings_default_rules">設置預設路由規則</string>
<string name="routing_settings_default_rules">設置默認路由規則</string>
<string name="connection_test_pending">"檢查連線能力"</string>
<string name="connection_test_testing">"測試中……"</string>
@@ -188,19 +178,8 @@
<string-array name="routing_mode">
<item>全球</item>
<item>略過域網</item>
<item>略過域網</item>
<item>略過中國大陸</item>
<item>略過域網及中國大陸</item>
<item>略過域網及中國大陸</item>
</string-array>
<string name="title_pref_proxy_sharing_enabled">代理共享</string>
<string name="summary_pref_proxy_sharing_enabled">綁定代理入口ip到0.0.0.0</string>
<string name="toast_warning_pref_proxysharing">其他設備可以使用socks/http協定通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網路中啟用以避免未經授權的連接</string>
<string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string>
<string name="toast_malformed_josn">配置格式錯誤</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>僅代理</item>
</string-array>
</resources>

Some files were not shown because too many files have changed in this diff Show More