Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb60e4a0e4 | ||
|
|
c3dfa8cedc | ||
|
|
f58bf85b6d | ||
|
|
906d0714b5 | ||
|
|
701fed2525 | ||
|
|
e83208465f | ||
|
|
2b031033d3 | ||
|
|
e0e16b5934 | ||
|
|
0748f994ef | ||
|
|
233b34bda6 | ||
|
|
6ece3385fe | ||
|
|
9b8a810445 | ||
|
|
f63242d147 | ||
|
|
7024fabb82 | ||
|
|
459f52fec6 | ||
|
|
90f2d33d97 | ||
|
|
115008a8a4 | ||
|
|
8cdc7fb3c9 | ||
|
|
d0a2fa0086 | ||
|
|
f6c54841d2 | ||
|
|
54fa356999 | ||
|
|
9642b7f64f | ||
|
|
dd2d2c1638 | ||
|
|
e21950dbcd | ||
|
|
d016ab06d4 | ||
|
|
4d9aced5a4 | ||
|
|
62b928e6a0 | ||
|
|
0ce60eae73 | ||
|
|
5930a6a9eb | ||
|
|
a360310be2 | ||
|
|
820e6cdf36 | ||
|
|
658b890325 | ||
|
|
fb017c6659 | ||
|
|
00e6314afe | ||
|
|
463f45804f | ||
|
|
572955dd1e | ||
|
|
375a209beb | ||
|
|
872f9ce199 | ||
|
|
b4f02c9bd6 | ||
|
|
e567719f5b | ||
|
|
8407fc5825 | ||
|
|
a3e49dcc3d | ||
|
|
7b47bbe99a | ||
|
|
0fb2165015 | ||
|
|
03eeeb9b62 | ||
|
|
038daf5fda | ||
|
|
bfd1387d9b | ||
|
|
5afec5cf25 | ||
|
|
ec29bdf5bf | ||
|
|
57efab093f | ||
|
|
9c92a64811 | ||
|
|
7ddc82d5cd | ||
|
|
c286ba18a8 | ||
|
|
867b5fc880 | ||
|
|
e8a7fa5320 | ||
|
|
f2f9e55286 | ||
|
|
4a1c62a67c | ||
|
|
c9a6a459d4 | ||
|
|
21fdcf4ccf | ||
|
|
7c7a623ae5 | ||
|
|
b3074e9697 | ||
|
|
513ebcfa23 | ||
|
|
50d9057f1a | ||
|
|
2a563e7884 | ||
|
|
c69cd18842 | ||
|
|
7f2ced85a8 | ||
|
|
6c5eef99b5 | ||
|
|
d7c3bae8cc | ||
|
|
57c98f7c50 | ||
|
|
49be23c56a | ||
|
|
9b658e9a22 | ||
|
|
aaa84d081f | ||
|
|
5628cbee3a | ||
|
|
3dd663d927 | ||
|
|
9e49a2dbd9 | ||
|
|
6c199c1687 | ||
|
|
39af5fdb86 | ||
|
|
7b2794f6be | ||
|
|
411d9e5c9a | ||
|
|
a57aee9424 | ||
|
|
4602afc67e | ||
|
|
ceb29840f2 | ||
|
|
1c1f130ca7 | ||
|
|
afa0eb9375 | ||
|
|
49b682f0f3 | ||
|
|
d5def3bf2f | ||
|
|
51b97b64f2 | ||
|
|
3af9ce1a1a | ||
|
|
a5d3dda941 | ||
|
|
71a3e300d8 | ||
|
|
030b9a3900 | ||
|
|
24d105a53c | ||
|
|
45805d6df7 | ||
|
|
4437e6699b | ||
|
|
fa409f91e4 | ||
|
|
89be5f077b | ||
|
|
896889778f | ||
|
|
10f705a8b2 | ||
|
|
2956fa2030 | ||
|
|
c2a704a6ea | ||
|
|
78cac0cd90 | ||
|
|
3ffb2e8e05 | ||
|
|
e2d667e0bb | ||
|
|
3ae0777d7f | ||
|
|
5e6348676c | ||
|
|
df3f1ca3ef | ||
|
|
94f2bec329 | ||
|
|
c54d8fa43a | ||
|
|
5bbbdcf6f2 | ||
|
|
4abf20fa32 | ||
|
|
723727feb9 | ||
|
|
2efd4b741c | ||
|
|
83aab0f880 | ||
|
|
66ea17877e | ||
|
|
26bc985368 | ||
|
|
5bbf40c784 | ||
|
|
6d5c23245c | ||
|
|
b148290211 | ||
|
|
c473f9bb13 | ||
|
|
c7c3d27f36 | ||
|
|
bebc6fea13 | ||
|
|
9271857b1e | ||
|
|
7ea78c1840 | ||
|
|
ac668788b3 | ||
|
|
adfcf0a5d9 | ||
|
|
15b5595797 | ||
|
|
5a18296cb2 | ||
|
|
1256edbaf5 | ||
|
|
870950e807 | ||
|
|
e798cb3f42 | ||
|
|
b970f0bcff | ||
|
|
93b9a56428 | ||
|
|
eb8bd13266 | ||
|
|
d850c88c63 | ||
|
|
6bee795c0d | ||
|
|
d7d7e029e0 | ||
|
|
8efdab43d7 | ||
|
|
5e0235cf70 | ||
|
|
9f668b3da7 | ||
|
|
b2437279dc | ||
|
|
180b5efd93 | ||
|
|
dc31380cc2 | ||
|
|
1361e0dacf | ||
|
|
a45eb66bd1 | ||
|
|
508102cebe | ||
|
|
1d86dbb9f3 | ||
|
|
20ca554be2 | ||
|
|
25ba455656 | ||
|
|
17e7c62d53 | ||
|
|
6f0f2fdeda | ||
|
|
3c9c9b5a4c | ||
|
|
e13024d6bb | ||
|
|
9d58edd31f | ||
|
|
af7dfc3a43 | ||
|
|
ca554e6ac4 | ||
|
|
ec391d8689 | ||
|
|
6afd4d0549 | ||
|
|
957cf85362 | ||
|
|
881152e10a | ||
|
|
6e47ebb27a | ||
|
|
a731e6c360 | ||
|
|
d1466ba4b2 | ||
|
|
617f28f399 | ||
|
|
c0ec0d9404 | ||
|
|
3419dc8837 | ||
|
|
786aaf823a | ||
|
|
1144183da4 | ||
|
|
5bf2fda990 | ||
|
|
28639cc388 | ||
|
|
ca254b2aa1 | ||
|
|
9721879713 | ||
|
|
623c1807c5 | ||
|
|
739fa88ba7 | ||
|
|
85d6f00f8c | ||
|
|
8986710453 | ||
|
|
f54faacbf6 | ||
|
|
993ee0b8d2 | ||
|
|
903352ec9c | ||
|
|
ad56106c08 | ||
|
|
91b8284afd | ||
|
|
ef9e0cc0d2 | ||
|
|
6577c46a31 | ||
|
|
c6dab001b2 | ||
|
|
aea8369b8a | ||
|
|
92d2cb35c4 | ||
|
|
6ce3d540e8 | ||
|
|
c105d84b35 | ||
|
|
8b149fb52f | ||
|
|
3ea04c076c | ||
|
|
98475460bf | ||
|
|
68ee61a753 | ||
|
|
90ba9ef2b7 | ||
|
|
0ec114322e | ||
|
|
76f52e7aa7 | ||
|
|
caa25ce424 | ||
|
|
e33d7e9bcf | ||
|
|
e120fce0b7 |
@@ -1,3 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: v2rayNG程序问题
|
||||||
|
about: 创建一个报告来帮助我们改进
|
||||||
|
---
|
||||||
|
|
||||||
在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。
|
在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。
|
||||||
|
|
||||||
|
|
||||||
@@ -14,7 +19,8 @@
|
|||||||
|
|
||||||
### 日志信息
|
### 日志信息
|
||||||
<details>
|
<details>
|
||||||
通过 `adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main` 获取日志。请自行删减日志中可能出现的敏感信息。
|
|
||||||
|
通过`adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main`获取日志。请自行删减日志中可能出现的敏感信息。
|
||||||
|
|
||||||
如果问题可重现,建议先执行`adb logcat -c`清空系统日志再执行上述命令,再操作重现问题。
|
如果问题可重现,建议先执行`adb logcat -c`清空系统日志再执行上述命令,再操作重现问题。
|
||||||
```
|
```
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: V2Ray程序问题
|
||||||
|
url: https://github.com/v2fly/v2ray-core/
|
||||||
|
about: 如果您有V2Ray而非v2rayNG的问题,请至这个链接讨论。
|
||||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,10 +2,8 @@ V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml
|
|||||||
V2rayNG/app/src/main/assets/geoip.dat
|
V2rayNG/app/src/main/assets/geoip.dat
|
||||||
V2rayNG/app/src/main/assets/geosite.dat
|
V2rayNG/app/src/main/assets/geosite.dat
|
||||||
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
|
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
|
||||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
|
||||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
|
||||||
*.dat
|
*.dat
|
||||||
*.jks
|
*.jks
|
||||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
V2rayNG/app/release/output.json
|
||||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
.idea/
|
||||||
V2rayNG/app/release/output.json
|
.gradle/
|
||||||
2
AndroidLibV2rayLite/.gitignore
vendored
Normal file
2
AndroidLibV2rayLite/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.jar
|
||||||
|
*.aar
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
sudo: required
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- "1.12"
|
|
||||||
go_import_path: github.com/2dust/AndroidLibV2rayLite
|
|
||||||
git:
|
|
||||||
depth: 5
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
before_script:
|
|
||||||
- sudo ntpdate -u time.google.com
|
|
||||||
- date
|
|
||||||
- make all
|
|
||||||
- make downloadGoMobile
|
|
||||||
script:
|
|
||||||
- make BuildMobile
|
|
||||||
after_success:
|
|
||||||
deploy:
|
|
||||||
provider: releases
|
|
||||||
api_key: ${GH_TOKEN}
|
|
||||||
file:
|
|
||||||
- libv2ray.aar
|
|
||||||
skip_cleanup: true
|
|
||||||
on:
|
|
||||||
tags: true
|
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
package CoreI
|
package CoreI
|
||||||
|
|
||||||
import (
|
import (
|
||||||
v2core "v2ray.com/core"
|
v2core "github.com/xtls/xray-core/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
IsRunning bool
|
IsRunning bool
|
||||||
PackageName string
|
IsTRunning bool
|
||||||
|
PackageName string
|
||||||
|
PackageCodePath string
|
||||||
|
|
||||||
Vpoint v2core.Server
|
Vpoint v2core.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckVersion() int {
|
func CheckVersion() int {
|
||||||
return 20
|
return 23
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Status) GetDataDir() string {
|
func (v *Status) GetDataDir() string {
|
||||||
@@ -20,7 +22,7 @@ func (v *Status) GetDataDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Status) GetApp(name string) string {
|
func (v *Status) GetApp(name string) string {
|
||||||
return v.PackageName + name
|
return v.PackageCodePath + name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
|
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
|
||||||
|
|||||||
@@ -8,27 +8,19 @@ asset:
|
|||||||
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat
|
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat
|
||||||
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat
|
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat
|
||||||
|
|
||||||
shippedBinary:
|
|
||||||
cd shippedBinarys; $(MAKE) shippedBinary
|
|
||||||
|
|
||||||
fetchDep:
|
|
||||||
-go get github.com/2dust/AndroidLibV2rayLite
|
|
||||||
go get github.com/2dust/AndroidLibV2rayLite
|
|
||||||
|
|
||||||
ANDROID_HOME=$(HOME)/android-sdk-linux
|
ANDROID_HOME=$(HOME)/android-sdk-linux
|
||||||
export ANDROID_HOME
|
export ANDROID_HOME
|
||||||
PATH:=$(PATH):$(GOPATH)/bin
|
PATH:=$(PATH):$(GOPATH)/bin
|
||||||
export PATH
|
export PATH
|
||||||
downloadGoMobile:
|
downloadGoMobile:
|
||||||
go get golang.org/x/mobile/cmd/...
|
|
||||||
sudo apt-get install -qq libstdc++6:i386 lib32z1 expect
|
sudo apt-get install -qq libstdc++6:i386 lib32z1 expect
|
||||||
cd ~ ;curl -L https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/ubuntu-cli-install-android-sdk.sh | sudo bash - > /dev/null
|
cd ~ ;curl -L https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/ubuntu-cli-install-android-sdk.sh | sudo bash - > /dev/null
|
||||||
ls ~
|
ls ~
|
||||||
ls ~/android-sdk-linux/
|
ls ~/android-sdk-linux/
|
||||||
gomobile init ;gomobile bind -v -tags json github.com/2dust/AndroidLibV2rayLite
|
|
||||||
|
|
||||||
BuildMobile:
|
BuildMobile:
|
||||||
@echo Stub
|
gomobile init
|
||||||
|
gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
|
||||||
|
|
||||||
all: asset pb shippedBinary fetchDep
|
all: asset pb
|
||||||
@echo DONE
|
@echo DONE
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ package Escort
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"time"
|
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, sendFd func() int) {
|
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string) {
|
||||||
log.Println(proc, pt)
|
log.Println(proc, pt)
|
||||||
count := 0
|
count := 0
|
||||||
for count <= 42 {
|
for count <= 42 {
|
||||||
@@ -37,13 +36,6 @@ func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, se
|
|||||||
*v.escortProcess = append(*v.escortProcess, cmd.Process)
|
*v.escortProcess = append(*v.escortProcess, cmd.Process)
|
||||||
log.Println("EscortRun Waiting....")
|
log.Println("EscortRun Waiting....")
|
||||||
|
|
||||||
if count > 0 {
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
sendFd()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
log.Println("EscortRun cmd.Wait err:", err)
|
log.Println("EscortRun cmd.Wait err:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
v2net "v2ray.com/core/common/net"
|
v2net "github.com/xtls/xray-core/common/net"
|
||||||
v2internet "v2ray.com/core/transport/internet"
|
"github.com/xtls/xray-core/features/dns"
|
||||||
|
"github.com/xtls/xray-core/features/outbound"
|
||||||
|
v2internet "github.com/xtls/xray-core/transport/internet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type protectSet interface {
|
type protectSet interface {
|
||||||
@@ -190,6 +192,11 @@ func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init implement internet.SystemDialer
|
||||||
|
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
// Dial exported as the protected dial method
|
// Dial exported as the protected dial method
|
||||||
func (d *ProtectedDialer) Dial(ctx context.Context,
|
func (d *ProtectedDialer) Dial(ctx context.Context,
|
||||||
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {
|
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
v2net "v2ray.com/core/common/net"
|
v2net "github.com/xtls/xray-core/common/net"
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeSupportSet struct{}
|
type fakeSupportSet struct{}
|
||||||
|
|||||||
18
AndroidLibV2rayLite/compile-tun2socks.sh
Normal file → Executable file
18
AndroidLibV2rayLite/compile-tun2socks.sh
Normal file → Executable file
@@ -23,8 +23,8 @@ trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp
|
|||||||
install -m644 $__dir/tun2socks.mk $TMPDIR/
|
install -m644 $__dir/tun2socks.mk $TMPDIR/
|
||||||
|
|
||||||
pushd $TMPDIR
|
pushd $TMPDIR
|
||||||
git clone --depth=1 https://github.com/shadowsocks/badvpn.git
|
git clone --depth=1 https://github.com/XTLS/badvpn.git
|
||||||
git clone --depth=1 https://github.com/shadowsocks/libancillary.git
|
git clone --depth=1 https://github.com/XTLS/libancillary.git
|
||||||
$NDK_HOME/ndk-build \
|
$NDK_HOME/ndk-build \
|
||||||
NDK_PROJECT_PATH=. \
|
NDK_PROJECT_PATH=. \
|
||||||
APP_BUILD_SCRIPT=./tun2socks.mk \
|
APP_BUILD_SCRIPT=./tun2socks.mk \
|
||||||
@@ -34,14 +34,10 @@ $NDK_HOME/ndk-build \
|
|||||||
NDK_OUT=$TMPDIR/tmp \
|
NDK_OUT=$TMPDIR/tmp \
|
||||||
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
|
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
|
||||||
|
|
||||||
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/shippedBinarys/ArchDep/arm/
|
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/shippedBinarys/ArchDep/arm64/
|
install -v -m755 libs/arm64-v8a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
|
||||||
install -v -m755 libs/x86/tun2socks $__dir/shippedBinarys/ArchDep/386/
|
install -v -m755 libs/x86/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
|
||||||
install -v -m755 libs/x86_64/tun2socks $__dir/shippedBinarys/ArchDep/amd64/
|
install -v -m755 libs/x86_64/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd $__dir/shippedBinarys
|
rm -rf $TMPDIR
|
||||||
make clean && make shippedBinary
|
|
||||||
popd
|
|
||||||
|
|
||||||
rm -rf $TMPDIR
|
|
||||||
|
|||||||
9
AndroidLibV2rayLite/go.mod
Normal file
9
AndroidLibV2rayLite/go.mod
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module github.com/2dust/AndroidLibV2rayLite
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/xtls/xray-core v1.4.2
|
||||||
|
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08
|
||||||
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
|
||||||
|
)
|
||||||
303
AndroidLibV2rayLite/go.sum
Normal file
303
AndroidLibV2rayLite/go.sum
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
||||||
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
|
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lucas-clemente/quic-go v0.20.0/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
|
||||||
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||||
|
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
||||||
|
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
|
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
|
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||||
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
|
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||||
|
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||||
|
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||||
|
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||||
|
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||||
|
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||||
|
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||||
|
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
|
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||||
|
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||||
|
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||||
|
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||||
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
|
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
|
||||||
|
github.com/xtls/xray-core v1.4.2 h1:D0Le+Qy9L/eY5LbUQfrk7WJ8wbODpQSW/ZRCg+BRe7c=
|
||||||
|
github.com/xtls/xray-core v1.4.2/go.mod h1:DmL/9rOCliev/a6HciWEvSJVEhUF6C0EpD3clW8v0pc=
|
||||||
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
|
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
|
||||||
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
|
||||||
|
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
||||||
|
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
|
h12.io/socks v1.0.2/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
@@ -11,22 +11,21 @@ import (
|
|||||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
||||||
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
|
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
|
||||||
"github.com/2dust/AndroidLibV2rayLite/VPN"
|
"github.com/2dust/AndroidLibV2rayLite/VPN"
|
||||||
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
|
|
||||||
mobasset "golang.org/x/mobile/asset"
|
mobasset "golang.org/x/mobile/asset"
|
||||||
|
|
||||||
v2core "v2ray.com/core"
|
v2core "github.com/xtls/xray-core/core"
|
||||||
v2filesystem "v2ray.com/core/common/platform/filesystem"
|
v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
||||||
v2stats "v2ray.com/core/features/stats"
|
v2stats "github.com/xtls/xray-core/features/stats"
|
||||||
v2serial "v2ray.com/core/infra/conf/serial"
|
v2serial "github.com/xtls/xray-core/infra/conf/serial"
|
||||||
_ "v2ray.com/core/main/distro/all"
|
_ "github.com/xtls/xray-core/main/distro/all"
|
||||||
v2internet "v2ray.com/core/transport/internet"
|
v2internet "github.com/xtls/xray-core/transport/internet"
|
||||||
|
|
||||||
v2applog "v2ray.com/core/app/log"
|
v2applog "github.com/xtls/xray-core/app/log"
|
||||||
v2commlog "v2ray.com/core/common/log"
|
v2commlog "github.com/xtls/xray-core/common/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
v2Assert = "v2ray.location.asset"
|
v2Assert = "xray.location.asset"
|
||||||
assetperfix = "/dev/libv2rayfs0/asset"
|
assetperfix = "/dev/libv2rayfs0/asset"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -44,10 +43,12 @@ type V2RayPoint struct {
|
|||||||
closeChan chan struct{}
|
closeChan chan struct{}
|
||||||
|
|
||||||
PackageName string
|
PackageName string
|
||||||
|
PackageCodePath string
|
||||||
DomainName string
|
DomainName string
|
||||||
ConfigureFileContent string
|
ConfigureFileContent string
|
||||||
EnableLocalDNS bool
|
EnableLocalDNS bool
|
||||||
ForwardIpv6 bool
|
ForwardIpv6 bool
|
||||||
|
ProxyOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
||||||
@@ -57,7 +58,6 @@ type V2RayVPNServiceSupportsSet interface {
|
|||||||
Shutdown() int
|
Shutdown() int
|
||||||
Protect(int) int
|
Protect(int) int
|
||||||
OnEmitStatus(int, string) int
|
OnEmitStatus(int, string) int
|
||||||
SendFd() int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*RunLoop Run V2Ray main loop
|
/*RunLoop Run V2Ray main loop
|
||||||
@@ -67,6 +67,7 @@ func (v *V2RayPoint) RunLoop() (err error) {
|
|||||||
defer v.v2rayOP.Unlock()
|
defer v.v2rayOP.Unlock()
|
||||||
//Construct Context
|
//Construct Context
|
||||||
v.status.PackageName = v.PackageName
|
v.status.PackageName = v.PackageName
|
||||||
|
v.status.PackageCodePath = v.PackageCodePath
|
||||||
|
|
||||||
if !v.status.IsRunning {
|
if !v.status.IsRunning {
|
||||||
v.closeChan = make(chan struct{})
|
v.closeChan = make(chan struct{})
|
||||||
@@ -80,7 +81,6 @@ func (v *V2RayPoint) RunLoop() (err error) {
|
|||||||
if !v.dialer.IsVServerReady() {
|
if !v.dialer.IsVServerReady() {
|
||||||
log.Println("vServer cannot resolved, shutdown")
|
log.Println("vServer cannot resolved, shutdown")
|
||||||
v.StopLoop()
|
v.StopLoop()
|
||||||
v.SupportSet.Shutdown()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop waiting if manually closed
|
// stop waiting if manually closed
|
||||||
@@ -99,9 +99,7 @@ func (v *V2RayPoint) StopLoop() (err error) {
|
|||||||
v.v2rayOP.Lock()
|
v.v2rayOP.Lock()
|
||||||
defer v.v2rayOP.Unlock()
|
defer v.v2rayOP.Unlock()
|
||||||
if v.status.IsRunning {
|
if v.status.IsRunning {
|
||||||
close(v.closeChan)
|
|
||||||
v.shutdownInit()
|
v.shutdownInit()
|
||||||
v.SupportSet.OnEmitStatus(0, "Closed")
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -111,12 +109,16 @@ func (v *V2RayPoint) GetIsRunning() bool {
|
|||||||
return v.status.IsRunning
|
return v.status.IsRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *V2RayPoint) GetIsTRunning() bool {
|
||||||
|
return v.status.IsTRunning
|
||||||
|
}
|
||||||
|
|
||||||
//Delegate Funcation
|
//Delegate Funcation
|
||||||
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
||||||
if v.statsManager == nil {
|
if v.statsManager == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
counter := v.statsManager.GetCounter(fmt.Sprintf("inbound>>>%s>>>traffic>>>%s", tag, direct))
|
counter := v.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
|
||||||
if counter == nil {
|
if counter == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -124,24 +126,19 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *V2RayPoint) shutdownInit() {
|
func (v *V2RayPoint) shutdownInit() {
|
||||||
v.status.IsRunning = false
|
close(v.closeChan)
|
||||||
|
v.statsManager = nil
|
||||||
v.status.Vpoint.Close()
|
v.status.Vpoint.Close()
|
||||||
v.status.Vpoint = nil
|
v.status.Vpoint = nil
|
||||||
v.statsManager = nil
|
v.status.IsRunning = false
|
||||||
|
|
||||||
v.escorter.EscortingDown()
|
v.escorter.EscortingDown()
|
||||||
|
|
||||||
|
v.SupportSet.Shutdown()
|
||||||
|
v.SupportSet.OnEmitStatus(0, "Closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *V2RayPoint) pointloop() error {
|
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")
|
log.Println("loading v2ray config")
|
||||||
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -169,6 +166,21 @@ func (v *V2RayPoint) pointloop() error {
|
|||||||
v.SupportSet.Prepare()
|
v.SupportSet.Prepare()
|
||||||
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
|
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
|
||||||
v.SupportSet.OnEmitStatus(0, "Running")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,17 +237,10 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v V2RayPoint) runTun2socks() error {
|
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()
|
v.escorter.EscortingUp()
|
||||||
go v.escorter.EscortRun(
|
go v.escorter.EscortRun(
|
||||||
v.status.GetApp("tun2socks"),
|
v.status.GetApp("libtun2socks.so"),
|
||||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
|
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
|
||||||
v.SupportSet.SendFd)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,13 +0,0 @@
|
|||||||
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*
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
v2commlog "v2ray.com/core/common/log"
|
v2commlog "github.com/xtls/xray-core/common/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type consoleLogWriter struct {
|
type consoleLogWriter struct {
|
||||||
|
|||||||
30
README.md
30
README.md
@@ -1,5 +1,35 @@
|
|||||||
# v2rayNG
|
# v2rayNG
|
||||||
|
|
||||||
|
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
||||||
|
|
||||||
|
[](https://developer.android.com/about/versions/jelly-bean#android-4.2)
|
||||||
|
[](https://kotlinlang.org)
|
||||||
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
|
[](https://t.me/v2rayn)
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
|
<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" />
|
<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>
|
</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
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||||
buildToolsVersion '28.0.3'
|
buildToolsVersion buildToolsVer
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
targetCompatibility = "8"
|
targetCompatibility = "8"
|
||||||
@@ -20,34 +20,19 @@ android {
|
|||||||
versionName "1.0.2"
|
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 {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
zipAlignEnabled false
|
zipAlignEnabled false
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
signingConfig signingConfigs.release
|
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
zipAlignEnabled false
|
zipAlignEnabled false
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
signingConfig signingConfigs.release
|
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +40,10 @@ android {
|
|||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
splits {
|
splits {
|
||||||
abi {
|
abi {
|
||||||
enable true
|
enable true
|
||||||
@@ -79,11 +68,17 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
testImplementation 'junit:junit:4.13'
|
||||||
testImplementation 'junit:junit:4.12'
|
|
||||||
implementation project(':dpreference')
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||||
|
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
|
// Android support library
|
||||||
implementation "com.android.support:support-v4:$supportLibVersion"
|
implementation "com.android.support:support-v4:$supportLibVersion"
|
||||||
implementation "com.android.support:appcompat-v7:$supportLibVersion"
|
implementation "com.android.support:appcompat-v7:$supportLibVersion"
|
||||||
@@ -91,22 +86,19 @@ dependencies {
|
|||||||
implementation "com.android.support:cardview-v7:$supportLibVersion"
|
implementation "com.android.support:cardview-v7:$supportLibVersion"
|
||||||
implementation "com.android.support:preference-v7:$supportLibVersion"
|
implementation "com.android.support:preference-v7:$supportLibVersion"
|
||||||
implementation "com.android.support:recyclerview-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
|
// DSL
|
||||||
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
|
implementation 'com.tencent:mmkv-static:1.0.19'
|
||||||
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
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:rxjava:1.3.4'
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
|
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:core:1.9.8'
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
||||||
implementation 'com.beust:klaxon:3.0.1'
|
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
|
||||||
|
|
||||||
implementation(name: 'libv2ray', ext: 'aar')
|
implementation(name: 'libv2ray', ext: 'aar')
|
||||||
//implementation(name: 'tun2socks', ext: 'aar')
|
//implementation(name: 'tun2socks', ext: 'aar')
|
||||||
@@ -126,4 +118,4 @@ repositories {
|
|||||||
flatDir {
|
flatDir {
|
||||||
dirs 'libs'
|
dirs 'libs'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
3
V2rayNG/app/proguard-rules.pro
vendored
3
V2rayNG/app/proguard-rules.pro
vendored
@@ -44,9 +44,6 @@
|
|||||||
static void throwUninitializedPropertyAccessException(java.lang.String);
|
static void throwUninitializedPropertyAccessException(java.lang.String);
|
||||||
}
|
}
|
||||||
|
|
||||||
-dontwarn org.jetbrains.anko.internals.**
|
|
||||||
-keep class org.jetbrains.anko.internals.** { *;}
|
|
||||||
|
|
||||||
-dontwarn rx.internal.util.unsafe.**
|
-dontwarn rx.internal.util.unsafe.**
|
||||||
-keep class rx.internal.util.unsafe.** { *;}
|
-keep class rx.internal.util.unsafe.** { *;}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.v2ray.ang">
|
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.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
@@ -11,12 +27,14 @@
|
|||||||
<uses-permission android:name="com.android.vending.BILLING" />
|
<uses-permission android:name="com.android.vending.BILLING" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".AngApplication"
|
android:name=".AngApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
android:extractNativeLibs="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme">
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
@@ -24,20 +42,16 @@
|
|||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
@@ -46,13 +60,7 @@
|
|||||||
android:name=".ui.ServerActivity"
|
android:name=".ui.ServerActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
android:windowSoftInputMode="stateUnchanged" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.Server2Activity"
|
android:name=".ui.ServerCustomConfigActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
|
||||||
<activity
|
|
||||||
android:name=".ui.Server3Activity"
|
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
|
||||||
<activity
|
|
||||||
android:name=".ui.Server4Activity"
|
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
android:windowSoftInputMode="stateUnchanged" />
|
||||||
<activity android:name=".ui.SettingsActivity" />
|
<activity android:name=".ui.SettingsActivity" />
|
||||||
<activity android:name=".ui.PerAppProxyActivity" />
|
<activity android:name=".ui.PerAppProxyActivity" />
|
||||||
@@ -66,7 +74,11 @@
|
|||||||
|
|
||||||
<activity android:name=".ui.SubEditActivity" />
|
<activity android:name=".ui.SubEditActivity" />
|
||||||
<activity android:name=".ui.ScScannerActivity" />
|
<activity android:name=".ui.ScScannerActivity" />
|
||||||
<activity android:name=".ui.ScSwitchActivity" />
|
<activity
|
||||||
|
android:name=".ui.ScSwitchActivity"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
android:process=":RunSoLibV2RayDaemon"
|
||||||
|
android:theme="@style/AppTheme.NoActionBar.Translucent" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.V2RayVpnService"
|
android:name=".service.V2RayVpnService"
|
||||||
@@ -83,22 +95,30 @@
|
|||||||
android:value="true" />
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!--<receiver android:name=".receiver.WidgetProvider">-->
|
<service android:name=".service.V2RayProxyOnlyService"
|
||||||
<!--<meta-data-->
|
android:exported="false"
|
||||||
<!--android:name="android.appwidget.provider"-->
|
android:label="@string/app_name"
|
||||||
<!--android:resource="@xml/app_widget_provider" />-->
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
|
</service>
|
||||||
|
|
||||||
<!--<intent-filter>-->
|
<receiver android:name=".receiver.WidgetProvider"
|
||||||
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
<!--<action android:name="com.v2ray.ang.action.widget.click" />-->
|
<meta-data
|
||||||
<!--</intent-filter>-->
|
android:name="android.appwidget.provider"
|
||||||
<!--</receiver>-->
|
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>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.QSTileService"
|
android:name=".service.QSTileService"
|
||||||
android:icon="@drawable/ic_v"
|
android:icon="@drawable/ic_v"
|
||||||
android:label="@string/app_tile_name"
|
android:label="@string/app_tile_name"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -113,7 +133,8 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<receiver android:name=".receiver.TaskerReceiver">
|
<receiver android:name=".receiver.TaskerReceiver"
|
||||||
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -122,4 +143,4 @@
|
|||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,196 +1,241 @@
|
|||||||
com.android.chrome
|
|
||||||
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.wire
|
|
||||||
mark.via.gp
|
|
||||||
com.downloader.video.tumblr
|
|
||||||
com.sololearn
|
|
||||||
com.cygames.shadowverse
|
|
||||||
com.felixfilip.scpae
|
|
||||||
amanita_design.samorost3.gp
|
amanita_design.samorost3.gp
|
||||||
com.devolver.reigns2
|
android
|
||||||
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
|
au.com.shiftyjelly.pocketcasts
|
||||||
com.google.android.gms
|
bbc.mobile.news.ww
|
||||||
com.android.providers.telephony
|
|
||||||
com.resilio.sync
|
|
||||||
com.google.android.apps.googlevoice
|
|
||||||
com.discord
|
|
||||||
com.cradle.iitc_mobile
|
|
||||||
be.mygod.vpnhotspot
|
be.mygod.vpnhotspot
|
||||||
|
ch.protonmail.android
|
||||||
|
co.wanqu.android
|
||||||
com.alphainventor.filemanager
|
com.alphainventor.filemanager
|
||||||
|
com.amazon.kindle
|
||||||
|
com.amazon.mshop.android.shopping
|
||||||
|
com.android.chrome
|
||||||
com.android.providers.downloads
|
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.apkpure.aegon
|
||||||
|
com.apkupdater
|
||||||
|
com.app.pornhub
|
||||||
|
com.arthurivanets.owly
|
||||||
|
com.asahi.tida.tablet
|
||||||
|
com.authy.authy
|
||||||
|
com.avmovie
|
||||||
com.ballistiq.artstation
|
com.ballistiq.artstation
|
||||||
|
com.binance.dev
|
||||||
com.bitly.app
|
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.canary
|
||||||
com.chrome.dev
|
com.chrome.dev
|
||||||
|
com.cl.newt66y
|
||||||
|
com.cradle.iitc_mobile
|
||||||
|
com.cygames.shadowverse
|
||||||
com.devhd.feedly
|
com.devhd.feedly
|
||||||
|
com.devolver.reigns2
|
||||||
|
com.discord
|
||||||
|
com.downloader.video.tumblr
|
||||||
|
com.driverbrowser
|
||||||
com.dropbox.android
|
com.dropbox.android
|
||||||
|
com.duolingo
|
||||||
|
com.duckduckgo.mobile.android
|
||||||
|
com.dv.adm
|
||||||
com.estrongs.android.pop
|
com.estrongs.android.pop
|
||||||
com.estrongs.android.pop.pro
|
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.fastaccess.github
|
||||||
|
com.felixfilip.scpae
|
||||||
|
com.fireproofstudios.theroom4
|
||||||
com.firstrowria.pushnotificationtester
|
com.firstrowria.pushnotificationtester
|
||||||
|
com.flyersoft.moonreaderp
|
||||||
|
com.fooview.android.fooview
|
||||||
com.fvd.eversync
|
com.fvd.eversync
|
||||||
|
com.gameloft.android.anmp.glofta8hm
|
||||||
|
com.gameloft.android.anmp.glofta9hm
|
||||||
com.gianlu.aria2app
|
com.gianlu.aria2app
|
||||||
com.github.yeriomin.yalpstore
|
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.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.gsf
|
||||||
|
com.google.android.gsf.login
|
||||||
|
com.google.android.ims
|
||||||
|
com.google.android.inputmethod.latin
|
||||||
com.google.android.instantapps.supervisor
|
com.google.android.instantapps.supervisor
|
||||||
|
com.google.android.keep
|
||||||
|
com.google.android.music
|
||||||
com.google.android.ogyoutube
|
com.google.android.ogyoutube
|
||||||
com.google.android.partnersetup
|
com.google.android.partnersetup
|
||||||
|
com.google.android.play.games
|
||||||
|
com.google.android.street
|
||||||
com.google.android.syncadapters.calendar
|
com.google.android.syncadapters.calendar
|
||||||
com.google.android.syncadapters.contacts
|
com.google.android.syncadapters.contacts
|
||||||
|
com.google.android.talk
|
||||||
com.google.android.tts
|
com.google.android.tts
|
||||||
|
com.google.android.videos
|
||||||
|
com.google.android.youtube
|
||||||
|
com.google.ar.lens
|
||||||
com.hochan.coldsoup
|
com.hochan.coldsoup
|
||||||
com.ifttt.ifttt
|
com.ifttt.ifttt
|
||||||
com.imgur.mobile
|
com.imgur.mobile
|
||||||
com.innologica.inoreader
|
com.innologica.inoreader
|
||||||
|
com.instagram.android
|
||||||
com.instapaper.android
|
com.instapaper.android
|
||||||
com.jarvanh.vpntether
|
com.jarvanh.vpntether
|
||||||
|
com.kapp.youtube.final
|
||||||
|
com.klinker.android.twitter_l
|
||||||
|
com.lastpass.lpandroid
|
||||||
|
com.linecorp.linelite
|
||||||
|
com.lingodeer
|
||||||
com.mediapods.tumbpods
|
com.mediapods.tumbpods
|
||||||
com.mgoogle.android.gms
|
com.mgoogle.android.gms
|
||||||
|
com.microsoft.emmx
|
||||||
com.microsoft.office.powerpoint
|
com.microsoft.office.powerpoint
|
||||||
|
com.microsoft.skydrive
|
||||||
com.mixplorer
|
com.mixplorer
|
||||||
com.msd.consumerchinese
|
com.msd.consumerchinese
|
||||||
com.msd.professionalchinese
|
com.msd.professionalchinese
|
||||||
com.mss2011c.sharehelper
|
com.mss2011c.sharehelper
|
||||||
|
com.netflix.mediaclient
|
||||||
com.newin.nplayer.pro
|
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.oasisfeng.island
|
||||||
|
com.ocnt.liveapp.hw
|
||||||
com.orekie.search
|
com.orekie.search
|
||||||
|
com.patreon.android
|
||||||
|
com.paypal.android.p2pmobile
|
||||||
|
com.perol.asdpl.pixivez
|
||||||
|
com.pinterest
|
||||||
|
com.popularapp.periodcalendar
|
||||||
com.popularapp.videodownloaderforinstagram
|
com.popularapp.videodownloaderforinstagram
|
||||||
com.pushbullet.android
|
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.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.slack
|
||||||
|
com.snaptube.premium
|
||||||
|
com.sololearn
|
||||||
|
com.sonelli.juicessh
|
||||||
|
com.spotify.music
|
||||||
com.tencent.huatuo
|
com.tencent.huatuo
|
||||||
com.termux
|
com.termux
|
||||||
|
com.teslacoilsw.launcher
|
||||||
|
com.theinitium.news
|
||||||
|
com.thomsonreuters.reuters
|
||||||
com.thunkable.android.hritvik00.freenom
|
com.thunkable.android.hritvik00.freenom
|
||||||
com.topjohnwu.magisk
|
com.topjohnwu.magisk
|
||||||
|
com.tripadvisor.tripadvisor
|
||||||
|
com.tumblr
|
||||||
|
com.twitter.android
|
||||||
com.u91porn
|
com.u91porn
|
||||||
com.u9porn
|
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.vimeo.android.videoapp
|
||||||
|
com.vivaldi.browser
|
||||||
|
com.vivaldi.browser.snapshot
|
||||||
|
com.vkontakte.android
|
||||||
|
com.whatsapp
|
||||||
|
com.wire
|
||||||
com.wuxiangai.refactor
|
com.wuxiangai.refactor
|
||||||
|
com.xda.labs
|
||||||
|
com.xvideos.app
|
||||||
com.yandex.browser
|
com.yandex.browser
|
||||||
|
com.yandex.browser.beta
|
||||||
|
com.yandex.browser.alpha
|
||||||
com.z28j.feel
|
com.z28j.feel
|
||||||
|
con.medium.reader
|
||||||
|
de.apkgrabber
|
||||||
de.robv.android.xposed.installer
|
de.robv.android.xposed.installer
|
||||||
dk.tacit.android.foldersync.full
|
dk.tacit.android.foldersync.full
|
||||||
es.rafalense.telegram.themes
|
es.rafalense.telegram.themes
|
||||||
es.rafalense.themes
|
es.rafalense.themes
|
||||||
flipboard.app
|
flipboard.app
|
||||||
|
fm.moon.app
|
||||||
|
fr.gouv.etalab.mastodon
|
||||||
github.tornaco.xposedmoduletest
|
github.tornaco.xposedmoduletest
|
||||||
|
idm.internet.download.manager
|
||||||
|
idm.internet.download.manager.plus
|
||||||
|
io.github.javiewer
|
||||||
|
io.github.skyhacker2.magnetsearch
|
||||||
io.va.exposed
|
io.va.exposed
|
||||||
|
it.mvilla.android.fenix2
|
||||||
|
jp.bokete.app.android
|
||||||
|
jp.naver.line.android
|
||||||
jp.pxv.android
|
jp.pxv.android
|
||||||
|
luo.speedometergpspro
|
||||||
|
mark.via.gp
|
||||||
me.tshine.easymark
|
me.tshine.easymark
|
||||||
net.teeha.android.url_shortener
|
net.teeha.android.url_shortener
|
||||||
|
net.tsapps.appsales
|
||||||
onion.fire
|
onion.fire
|
||||||
org.fdroid.fdroid
|
org.fdroid.fdroid
|
||||||
|
org.freedownloadmanager.fdm
|
||||||
|
org.kustom.widget
|
||||||
org.mozilla.fennec_aurora
|
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.schabi.newpipe
|
||||||
org.telegram.messenger
|
org.telegram.messenger
|
||||||
|
org.telegram.multi
|
||||||
|
org.telegram.plus
|
||||||
|
org.thunderdog.challegram
|
||||||
org.torproject.android
|
org.torproject.android
|
||||||
|
org.torproject.torbrowser_alpha
|
||||||
|
org.wikipedia
|
||||||
org.xbmc.kodi
|
org.xbmc.kodi
|
||||||
pl.zdunex25.updater
|
pl.zdunex25.updater
|
||||||
videodownloader.downloadvideo.downloader
|
|
||||||
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
|
tv.twitch.android.app
|
||||||
com.shanga.walli
|
tw.com.gamer.android.activecenter
|
||||||
com.whatsapp
|
videodownloader.downloadvideo.downloader
|
||||||
com.wire
|
uk.co.bbc.learningenglish
|
||||||
com.simplehabit.simplehabitapp
|
com.ted.android
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"statsInboundUplink": true,
|
"statsOutboundUplink": true,
|
||||||
"statsInboundDownlink": true
|
"statsOutboundDownlink": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inbounds": [{
|
"inbounds": [{
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ public interface ItemTouchHelperAdapter {
|
|||||||
boolean onItemMove(int fromPosition, int toPosition);
|
boolean onItemMove(int fromPosition, int toPosition);
|
||||||
|
|
||||||
|
|
||||||
|
void onItemMoveCompleted();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an item has been dismissed by a swipe.<br/>
|
* Called when an item has been dismissed by a swipe.<br/>
|
||||||
* <br/>
|
* <br/>
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
|||||||
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||||
super.clearView(recyclerView, viewHolder);
|
super.clearView(recyclerView, viewHolder);
|
||||||
|
|
||||||
|
mAdapter.onItemMoveCompleted();
|
||||||
|
|
||||||
viewHolder.itemView.setAlpha(ALPHA_FULL);
|
viewHolder.itemView.setAlpha(ALPHA_FULL);
|
||||||
|
|
||||||
if (viewHolder instanceof ItemTouchHelperViewHolder) {
|
if (viewHolder instanceof ItemTouchHelperViewHolder) {
|
||||||
|
|||||||
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
Executable file
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
Executable file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
Executable file
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
Executable file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
Executable file
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
Executable file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
Executable file
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
Executable file
Binary file not shown.
@@ -1,12 +1,10 @@
|
|||||||
package com.v2ray.ang
|
package com.v2ray.ang
|
||||||
|
|
||||||
import android.app.Application
|
import android.support.multidex.MultiDexApplication
|
||||||
//import com.squareup.leakcanary.LeakCanary
|
import android.support.v7.preference.PreferenceManager
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
import com.tencent.mmkv.MMKV
|
||||||
import me.dozen.dpreference.DPreference
|
|
||||||
import org.jetbrains.anko.defaultSharedPreferences
|
|
||||||
|
|
||||||
class AngApplication : Application() {
|
class AngApplication : MultiDexApplication() {
|
||||||
companion object {
|
companion object {
|
||||||
const val PREF_LAST_VERSION = "pref_last_version"
|
const val PREF_LAST_VERSION = "pref_last_version"
|
||||||
}
|
}
|
||||||
@@ -15,18 +13,17 @@ class AngApplication : Application() {
|
|||||||
var firstRun = false
|
var firstRun = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
// LeakCanary.install(this)
|
// LeakCanary.install(this)
|
||||||
|
|
||||||
|
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
|
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
|
||||||
if (firstRun)
|
if (firstRun)
|
||||||
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
|
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
|
||||||
|
|
||||||
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
|
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
|
||||||
AngConfigManager.inject(this)
|
MMKV.initialize(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,46 @@ package com.v2ray.ang
|
|||||||
*/
|
*/
|
||||||
object AppConfig {
|
object AppConfig {
|
||||||
const val ANG_PACKAGE = "com.v2ray.ang"
|
const val ANG_PACKAGE = "com.v2ray.ang"
|
||||||
|
|
||||||
|
// legacy
|
||||||
const val ANG_CONFIG = "ang_config"
|
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_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||||
const val VMESS_PROTOCOL: String = "vmess://"
|
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||||
const val SS_PROTOCOL: String = "ss://"
|
|
||||||
const val SOCKS_PROTOCOL: String = "socks://"
|
// Preferences mapped to MMKV
|
||||||
|
const val PREF_MODE = "pref_mode"
|
||||||
|
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||||
|
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||||
|
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||||
|
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
||||||
|
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
||||||
|
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||||
|
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||||
|
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||||
|
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||||
|
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
||||||
|
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||||
|
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||||
|
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||||
|
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
||||||
|
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
||||||
|
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||||
|
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||||
|
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||||
|
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
||||||
|
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
|
||||||
|
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
|
||||||
|
// const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||||
|
// const val PREF_HTTP_PORT = "pref_http_port"
|
||||||
|
// const val PREF_DONATE = "pref_donate"
|
||||||
|
// const val PREF_LICENSES = "pref_licenses"
|
||||||
|
// const val PREF_FEEDBACK = "pref_feedback"
|
||||||
|
// const val PREF_TG_GROUP = "pref_tg_group"
|
||||||
|
// const val PREF_AUTO_RESTART = "pref_auto_restart"
|
||||||
|
|
||||||
|
const val HTTP_PROTOCOL: String = "http://"
|
||||||
|
const val HTTPS_PROTOCOL: String = "https://"
|
||||||
|
|
||||||
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
||||||
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
||||||
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
||||||
@@ -25,9 +56,6 @@ object AppConfig {
|
|||||||
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
||||||
const val TASKER_DEFAULT_GUID = "Default"
|
const val TASKER_DEFAULT_GUID = "Default"
|
||||||
|
|
||||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
|
||||||
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
|
||||||
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
|
||||||
const val TAG_AGENT = "proxy"
|
const val TAG_AGENT = "proxy"
|
||||||
const val TAG_DIRECT = "direct"
|
const val TAG_DIRECT = "direct"
|
||||||
const val TAG_BLOCKED = "block"
|
const val TAG_BLOCKED = "block"
|
||||||
@@ -35,6 +63,7 @@ object AppConfig {
|
|||||||
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||||
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
||||||
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
|
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 promotionUrl = "https://1.2345345.xyz/ads.html"
|
||||||
|
|
||||||
const val DNS_AGENT = "1.1.1.1"
|
const val DNS_AGENT = "1.1.1.1"
|
||||||
@@ -50,12 +79,4 @@ object AppConfig {
|
|||||||
const val MSG_STATE_STOP = 4
|
const val MSG_STATE_STOP = 4
|
||||||
const val MSG_STATE_STOP_SUCCESS = 41
|
const val MSG_STATE_STOP_SUCCESS = 41
|
||||||
const val MSG_STATE_RESTART = 5
|
const val MSG_STATE_RESTART = 5
|
||||||
|
}
|
||||||
object EConfigType {
|
|
||||||
val Vmess = 1
|
|
||||||
val Custom = 2
|
|
||||||
val Shadowsocks = 3
|
|
||||||
val Socks = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
14
V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/EConfigType.kt
Normal file
14
V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/EConfigType.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
|
||||||
|
fun getTestDelayString(): String {
|
||||||
|
if (testDelayMillis == 0L) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return testDelayMillis.toString() + "ms"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
import com.v2ray.ang.AppConfig.TAG_AGENT
|
||||||
|
import com.v2ray.ang.AppConfig.TAG_BLOCKED
|
||||||
|
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
|
data class ServerConfig(
|
||||||
|
val configVersion: Int = 3,
|
||||||
|
val configType: EConfigType,
|
||||||
|
var subscriptionId: String = "",
|
||||||
|
val addedTime: Long = System.currentTimeMillis(),
|
||||||
|
var remarks: String = "",
|
||||||
|
val outboundBean: V2rayConfig.OutboundBean? = null,
|
||||||
|
var fullConfig: V2rayConfig? = null
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun create(configType: EConfigType): ServerConfig {
|
||||||
|
when(configType) {
|
||||||
|
EConfigType.VMESS, EConfigType.VLESS ->
|
||||||
|
return ServerConfig(
|
||||||
|
configType = configType,
|
||||||
|
outboundBean = V2rayConfig.OutboundBean(
|
||||||
|
protocol = configType.name.toLowerCase(),
|
||||||
|
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||||
|
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
|
||||||
|
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
|
||||||
|
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||||
|
EConfigType.CUSTOM ->
|
||||||
|
return ServerConfig(configType = configType)
|
||||||
|
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
|
||||||
|
return ServerConfig(
|
||||||
|
configType = configType,
|
||||||
|
outboundBean = V2rayConfig.OutboundBean(
|
||||||
|
protocol = configType.name.toLowerCase(),
|
||||||
|
settings = V2rayConfig.OutboundBean.OutSettingsBean(
|
||||||
|
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
|
||||||
|
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProxyOutbound(): V2rayConfig.OutboundBean? {
|
||||||
|
if (configType != EConfigType.CUSTOM) {
|
||||||
|
return outboundBean
|
||||||
|
}
|
||||||
|
return fullConfig?.getProxyOutbound()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllOutboundTags(): MutableList<String> {
|
||||||
|
if (configType != EConfigType.CUSTOM) {
|
||||||
|
return mutableListOf(TAG_AGENT, TAG_DIRECT, TAG_BLOCKED)
|
||||||
|
}
|
||||||
|
fullConfig?.let { config ->
|
||||||
|
return config.outbounds.map { it.tag }.toMutableList()
|
||||||
|
}
|
||||||
|
return mutableListOf()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getV2rayPointDomainAndPort(): String {
|
||||||
|
val address = getProxyOutbound()?.getServerAddress().orEmpty()
|
||||||
|
val port = getProxyOutbound()?.getServerPort()
|
||||||
|
return if (Utils.isIpv6Address(address)) {
|
||||||
|
String.format("[%s]:%s", address, port)
|
||||||
|
} else {
|
||||||
|
String.format("%s:%s", address, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class SubscriptionItem(
|
||||||
|
var remarks: String = "",
|
||||||
|
var url: String = "",
|
||||||
|
var enabled: Boolean = true,
|
||||||
|
val addedTime: Long = System.currentTimeMillis()) {
|
||||||
|
}
|
||||||
@@ -1,142 +1,447 @@
|
|||||||
package com.v2ray.ang.dto
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
import android.text.TextUtils
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import com.google.gson.JsonPrimitive
|
||||||
|
import com.google.gson.JsonSerializationContext
|
||||||
|
import com.google.gson.JsonSerializer
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
data class V2rayConfig(
|
data class V2rayConfig(
|
||||||
val stats: Any?=null,
|
var stats: Any? = null,
|
||||||
val log: LogBean,
|
val log: LogBean,
|
||||||
val policy: PolicyBean,
|
var policy: PolicyBean?,
|
||||||
val inbounds: ArrayList<InboundBean>,
|
val inbounds: ArrayList<InboundBean>,
|
||||||
var outbounds: ArrayList<OutboundBean>,
|
var outbounds: ArrayList<OutboundBean>,
|
||||||
var dns: DnsBean,
|
var dns: DnsBean,
|
||||||
val routing: RoutingBean) {
|
val routing: RoutingBean,
|
||||||
|
val api: Any? = null,
|
||||||
|
val transport: Any? = null,
|
||||||
|
val reverse: Any? = null,
|
||||||
|
var fakedns: FakednsBean? = null,
|
||||||
|
val browserForwarder: Any? = null) {
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_PORT = 443
|
||||||
|
const val DEFAULT_SECURITY = "auto"
|
||||||
|
const val DEFAULT_LEVEL = 8
|
||||||
|
const val DEFAULT_NETWORK = "tcp"
|
||||||
|
const val DEFAULT_FLOW = "xtls-rprx-splice"
|
||||||
|
|
||||||
|
const val TLS = "tls"
|
||||||
|
const val XTLS = "xtls"
|
||||||
|
const val HTTP = "http"
|
||||||
|
}
|
||||||
|
|
||||||
data class LogBean(val access: String,
|
data class LogBean(val access: String,
|
||||||
val error: String,
|
val error: String,
|
||||||
val loglevel: String)
|
var loglevel: String?,
|
||||||
|
val dnsLog: Boolean? = null)
|
||||||
|
|
||||||
data class InboundBean(
|
data class InboundBean(
|
||||||
var tag: String,
|
var tag: String,
|
||||||
var port: Int,
|
var port: Int,
|
||||||
var protocol: String,
|
var protocol: String,
|
||||||
var listen: String?=null,
|
var listen: String? = null,
|
||||||
val settings: InSettingsBean,
|
val settings: Any? = null,
|
||||||
val sniffing: SniffingBean?) {
|
val sniffing: SniffingBean?,
|
||||||
|
val streamSettings: Any? = null,
|
||||||
|
val allocate: Any? = null) {
|
||||||
|
|
||||||
data class InSettingsBean(val auth: String? = null,
|
data class InSettingsBean(val auth: String? = null,
|
||||||
val udp: Boolean? = null,
|
val udp: Boolean? = null,
|
||||||
val userLevel: Int? =null,
|
val userLevel: Int? = null,
|
||||||
val address: String? = null,
|
val address: String? = null,
|
||||||
val port: Int? = null,
|
val port: Int? = null,
|
||||||
val network: String? = null)
|
val network: String? = null)
|
||||||
|
|
||||||
data class SniffingBean(var enabled: Boolean,
|
data class SniffingBean(var enabled: Boolean,
|
||||||
val destOverride: List<String>)
|
val destOverride: ArrayList<String>,
|
||||||
|
val metadataOnly: Boolean? = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class OutboundBean(val tag: String,
|
data class OutboundBean(val tag: String = "proxy",
|
||||||
var protocol: String,
|
var protocol: String,
|
||||||
var settings: OutSettingsBean?,
|
var settings: OutSettingsBean? = null,
|
||||||
var streamSettings: StreamSettingsBean?,
|
var streamSettings: StreamSettingsBean? = null,
|
||||||
var mux: MuxBean?) {
|
val proxySettings: Any? = null,
|
||||||
|
val sendThrough: String? = null,
|
||||||
|
val mux: MuxBean? = MuxBean(false)) {
|
||||||
|
|
||||||
data class OutSettingsBean(var vnext: List<VnextBean>?,
|
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
|
||||||
var servers: List<ServersBean>?,
|
var servers: List<ServersBean>? = null,
|
||||||
var response: Response) {
|
/*Blackhole*/
|
||||||
|
var response: Response? = null,
|
||||||
|
/*DNS*/
|
||||||
|
val network: String? = null,
|
||||||
|
val address: String? = null,
|
||||||
|
val port: Int? = null,
|
||||||
|
/*Freedom*/
|
||||||
|
var domainStrategy: String? = null,
|
||||||
|
val redirect: String? = null,
|
||||||
|
val userLevel: Int? = null,
|
||||||
|
/*Loopback*/
|
||||||
|
val inboundTag: String? = null) {
|
||||||
|
|
||||||
data class VnextBean(var address: String,
|
data class VnextBean(var address: String = "",
|
||||||
var port: Int,
|
var port: Int = DEFAULT_PORT,
|
||||||
var users: List<UsersBean>) {
|
var users: List<UsersBean>) {
|
||||||
|
|
||||||
data class UsersBean(var id: String,
|
data class UsersBean(var id: String = "",
|
||||||
var alterId: Int,
|
var alterId: Int? = null,
|
||||||
var security: String,
|
var security: String = DEFAULT_SECURITY,
|
||||||
var level: Int)
|
var level: Int = DEFAULT_LEVEL,
|
||||||
|
var encryption: String = "",
|
||||||
|
var flow: String = "")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ServersBean(var address: String,
|
data class ServersBean(var address: String = "",
|
||||||
var method: String,
|
var method: String = "chacha20-poly1305",
|
||||||
var ota: Boolean,
|
var ota: Boolean = false,
|
||||||
var password: String,
|
var password: String = "",
|
||||||
var port: Int,
|
var port: Int = DEFAULT_PORT,
|
||||||
var level: Int)
|
var level: Int = DEFAULT_LEVEL,
|
||||||
|
val email: String? = null,
|
||||||
|
val flow: String? = null,
|
||||||
|
val ivCheck: Boolean? = null,
|
||||||
|
var users: List<SocksUsersBean>? = null) {
|
||||||
|
|
||||||
|
|
||||||
|
data class SocksUsersBean(var user: String = "",
|
||||||
|
var pass: String = "",
|
||||||
|
var level: Int = DEFAULT_LEVEL)
|
||||||
|
}
|
||||||
|
|
||||||
data class Response(var type: String)
|
data class Response(var type: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StreamSettingsBean(var network: String,
|
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
|
||||||
var security: String,
|
var security: String = "",
|
||||||
var tcpSettings: TcpsettingsBean?,
|
var tcpSettings: TcpSettingsBean? = null,
|
||||||
var kcpsettings: KcpsettingsBean?,
|
var kcpSettings: KcpSettingsBean? = null,
|
||||||
var wssettings: WssettingsBean?,
|
var wsSettings: WsSettingsBean? = null,
|
||||||
var httpsettings: HttpsettingsBean?,
|
var httpSettings: HttpSettingsBean? = null,
|
||||||
var tlssettings: TlssettingsBean?,
|
var tlsSettings: TlsSettingsBean? = null,
|
||||||
var quicsettings: QuicsettingBean?
|
var quicSettings: QuicSettingBean? = null,
|
||||||
|
var xtlsSettings: TlsSettingsBean? = null,
|
||||||
|
var grpcSettings: GrpcSettingsBean? = null,
|
||||||
|
val dsSettings: Any? = null,
|
||||||
|
val sockopt: Any? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class TcpsettingsBean(var connectionReuse: Boolean = true,
|
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
|
||||||
var header: HeaderBean = HeaderBean()) {
|
val acceptProxyProtocol: Boolean? = null) {
|
||||||
data class HeaderBean(var type: String = "none",
|
data class HeaderBean(var type: String = "none",
|
||||||
var request: Any? = null,
|
var request: RequestBean? = null,
|
||||||
var response: Any? = null)
|
var response: Any? = null) {
|
||||||
|
data class RequestBean(var path: List<String> = ArrayList(),
|
||||||
|
var headers: HeadersBean = HeadersBean(),
|
||||||
|
val version: String? = null,
|
||||||
|
val method: String? = null) {
|
||||||
|
data class HeadersBean(var Host: List<String> = ArrayList(),
|
||||||
|
@SerializedName("User-Agent")
|
||||||
|
val userAgent: List<String>? = null,
|
||||||
|
@SerializedName("Accept-Encoding")
|
||||||
|
val acceptEncoding: List<String>? = null,
|
||||||
|
val Connection: List<String>? = null,
|
||||||
|
val Pragma: String? = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class KcpsettingsBean(var mtu: Int = 1350,
|
data class KcpSettingsBean(var mtu: Int = 1350,
|
||||||
var tti: Int = 20,
|
var tti: Int = 50,
|
||||||
var uplinkCapacity: Int = 12,
|
var uplinkCapacity: Int = 12,
|
||||||
var downlinkCapacity: Int = 100,
|
var downlinkCapacity: Int = 100,
|
||||||
var congestion: Boolean = false,
|
var congestion: Boolean = false,
|
||||||
var readBufferSize: Int = 1,
|
var readBufferSize: Int = 1,
|
||||||
var writeBufferSize: Int = 1,
|
var writeBufferSize: Int = 1,
|
||||||
|
var header: HeaderBean = HeaderBean(),
|
||||||
|
var 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()) {
|
var header: HeaderBean = HeaderBean()) {
|
||||||
data class HeaderBean(var type: String = "none")
|
data class HeaderBean(var type: String = "none")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class WssettingsBean(var connectionReuse: Boolean = true,
|
data class GrpcSettingsBean(var serviceName: String = "",
|
||||||
var path: String = "",
|
var multiMode: Boolean? = null)
|
||||||
var headers: HeadersBean = HeadersBean()) {
|
|
||||||
data class HeadersBean(var Host: String = "")
|
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||||
|
quicSecurity: String?, key: String?, mode: String?, serviceName: String?): String {
|
||||||
|
var sni = ""
|
||||||
|
network = transport
|
||||||
|
when (network) {
|
||||||
|
"tcp" -> {
|
||||||
|
val tcpSetting = TcpSettingsBean()
|
||||||
|
if (headerType == HTTP) {
|
||||||
|
tcpSetting.header.type = HTTP
|
||||||
|
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
||||||
|
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
||||||
|
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }
|
||||||
|
requestObj.path = (path ?: "").split(",").map { it.trim() }
|
||||||
|
tcpSetting.header.request = requestObj
|
||||||
|
sni = requestObj.headers.Host.getOrNull(0) ?: sni
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tcpSetting.header.type = "none"
|
||||||
|
sni = host ?: ""
|
||||||
|
}
|
||||||
|
tcpSettings = tcpSetting
|
||||||
|
}
|
||||||
|
"kcp" -> {
|
||||||
|
val kcpsetting = KcpSettingsBean()
|
||||||
|
kcpsetting.header.type = headerType ?: "none"
|
||||||
|
if (seed.isNullOrEmpty()) {
|
||||||
|
kcpsetting.seed = null
|
||||||
|
} else {
|
||||||
|
kcpsetting.seed = seed
|
||||||
|
}
|
||||||
|
kcpSettings = kcpsetting
|
||||||
|
}
|
||||||
|
"ws" -> {
|
||||||
|
val wssetting = WsSettingsBean()
|
||||||
|
wssetting.headers.Host = host ?: ""
|
||||||
|
sni = wssetting.headers.Host
|
||||||
|
wssetting.path = path ?: "/"
|
||||||
|
wsSettings = wssetting
|
||||||
|
}
|
||||||
|
"h2", "http" -> {
|
||||||
|
network = "h2"
|
||||||
|
val h2Setting = HttpSettingsBean()
|
||||||
|
h2Setting.host = (host ?: "").split(",").map { it.trim() }
|
||||||
|
sni = h2Setting.host.getOrNull(0) ?: sni
|
||||||
|
h2Setting.path = path ?: "/"
|
||||||
|
httpSettings = h2Setting
|
||||||
|
}
|
||||||
|
"quic" -> {
|
||||||
|
val quicsetting = QuicSettingBean()
|
||||||
|
quicsetting.security = quicSecurity ?: "none"
|
||||||
|
quicsetting.key = key ?: ""
|
||||||
|
quicsetting.header.type = headerType ?: "none"
|
||||||
|
quicSettings = quicsetting
|
||||||
|
}
|
||||||
|
"grpc" -> {
|
||||||
|
val grpcSetting = GrpcSettingsBean()
|
||||||
|
grpcSetting.multiMode = mode == "multi"
|
||||||
|
grpcSetting.serviceName = serviceName ?: ""
|
||||||
|
sni = host ?: ""
|
||||||
|
grpcSettings = grpcSetting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sni
|
||||||
}
|
}
|
||||||
|
|
||||||
data class HttpsettingsBean(var host: List<String> = ArrayList<String>(), var path: String = "")
|
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String) {
|
||||||
|
security = streamSecurity
|
||||||
data class TlssettingsBean(var allowInsecure: Boolean = true,
|
val tlsSetting = TlsSettingsBean(
|
||||||
var serverName: String = "")
|
allowInsecure = allowInsecure,
|
||||||
|
serverName = sni
|
||||||
data class QuicsettingBean(var security: String = "none",
|
)
|
||||||
var key: String = "",
|
if (security == TLS) {
|
||||||
var header: HeaderBean = HeaderBean()) {
|
tlsSettings = tlsSetting
|
||||||
data class HeaderBean(var type: String = "none")
|
} else if (security == XTLS) {
|
||||||
|
xtlsSettings = tlsSetting
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class MuxBean(var enabled: Boolean)
|
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
|
||||||
|
|
||||||
|
fun getServerAddress(): String? {
|
||||||
|
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(if (grpcSetting.multiMode == true) "multi" else "gun",
|
||||||
|
"",
|
||||||
|
grpcSetting.serviceName)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//data class DnsBean(var servers: List<String>)
|
data class DnsBean(var servers: List<Any>? = null,
|
||||||
data class DnsBean(var servers: List<Any>?=null,
|
var hosts: Map<String, String>? = null,
|
||||||
var hosts: Map<String, String>?=null
|
val clientIp: String? = null,
|
||||||
|
val disableCache: Boolean? = null,
|
||||||
|
val queryStrategy: String? = null,
|
||||||
|
val tag: String? = null
|
||||||
) {
|
) {
|
||||||
data class ServersBean(var address: String = "",
|
data class ServersBean(var address: String = "",
|
||||||
var port: Int = 0,
|
var port: Int? = null,
|
||||||
var domains: List<String>?)
|
var domains: List<String>? = null,
|
||||||
|
var expectIPs: List<String>? = null,
|
||||||
|
val clientIp: String? = null)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RoutingBean(var domainStrategy: String,
|
data class RoutingBean(var domainStrategy: String,
|
||||||
var rules: ArrayList<RulesBean>) {
|
val domainMatcher: String? = null,
|
||||||
|
var rules: ArrayList<RulesBean>,
|
||||||
|
val balancers: List<Any>? = null) {
|
||||||
|
|
||||||
data class RulesBean(var type: String = "",
|
data class RulesBean(var type: String = "",
|
||||||
var ip: ArrayList<String>? = null,
|
var ip: ArrayList<String>? = null,
|
||||||
var domain: ArrayList<String>? = null,
|
var domain: ArrayList<String>? = null,
|
||||||
var outboundTag: String = "",
|
var outboundTag: String = "",
|
||||||
|
var balancerTag: String? = null,
|
||||||
var port: String? = null,
|
var port: String? = null,
|
||||||
var inboundTag: ArrayList<String>? = null)
|
val sourcePort: String? = null,
|
||||||
|
val network: String? = null,
|
||||||
|
val source: List<String>? = null,
|
||||||
|
val user: List<String>? = null,
|
||||||
|
var inboundTag: List<String>? = null,
|
||||||
|
val protocol: List<String>? = null,
|
||||||
|
val attrs: String? = null,
|
||||||
|
val domainMatcher: String? = null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class PolicyBean(var levels: Map<String, LevelBean>,
|
data class PolicyBean(var levels: Map<String, LevelBean>,
|
||||||
var system: Any?=null) {
|
var system: Any? = null) {
|
||||||
data class LevelBean(
|
data class LevelBean(
|
||||||
var handshake: Int? = null,
|
var handshake: Int? = null,
|
||||||
var connIdle: Int? = null,
|
var connIdle: Int? = null,
|
||||||
var uplinkOnly: Int? = null,
|
var uplinkOnly: Int? = null,
|
||||||
var downlinkOnly: Int? = null)
|
var downlinkOnly: Int? = null,
|
||||||
|
val statsUserUplink: Boolean? = null,
|
||||||
|
val statsUserDownlink: Boolean? = null,
|
||||||
|
var bufferSize: Int? = null)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
data class FakednsBean(var ipPool: String = "198.18.0.0/15",
|
||||||
|
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
|
||||||
|
|
||||||
|
fun getProxyOutbound(): OutboundBean? {
|
||||||
|
outbounds.forEach { outbound ->
|
||||||
|
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
|
||||||
|
outbound.protocol.equals(EConfigType.VLESS.name, true) ||
|
||||||
|
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
|
||||||
|
outbound.protocol.equals(EConfigType.SOCKS.name, true) ||
|
||||||
|
outbound.protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||||
|
return outbound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toPrettyPrinting(): String {
|
||||||
|
return GsonBuilder()
|
||||||
|
.setPrettyPrinting()
|
||||||
|
.disableHtmlEscaping()
|
||||||
|
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
|
||||||
|
object : TypeToken<Double>() {}.type,
|
||||||
|
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? -> JsonPrimitive(src?.toInt()) }
|
||||||
|
)
|
||||||
|
.create()
|
||||||
|
.toJson(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ data class VmessQRCode(var v: String = "",
|
|||||||
var type: String = "",
|
var type: String = "",
|
||||||
var host: String = "",
|
var host: String = "",
|
||||||
var path: String = "",
|
var path: String = "",
|
||||||
var tls: String = "")
|
var tls: String = "",
|
||||||
|
var sni: String = "")
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package com.v2ray.ang.extension
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.widget.Toast
|
||||||
import com.v2ray.ang.AngApplication
|
import com.v2ray.ang.AngApplication
|
||||||
import me.dozen.dpreference.DPreference
|
import me.drakeet.support.toast.ToastCompat
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
|
|
||||||
@@ -14,11 +15,19 @@ import java.net.URLConnection
|
|||||||
val Context.v2RayApplication: AngApplication
|
val Context.v2RayApplication: AngApplication
|
||||||
get() = applicationContext as AngApplication
|
get() = applicationContext as AngApplication
|
||||||
|
|
||||||
val Context.defaultDPreference: DPreference
|
inline fun Context.toast(message: Int): Toast = ToastCompat
|
||||||
get() = v2RayApplication.defaultDPreference
|
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||||
|
.apply {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
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) }
|
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
|
||||||
|
|
||||||
const val threshold = 1000
|
const val threshold = 1000
|
||||||
@@ -27,34 +36,37 @@ const val divisor = 1024F
|
|||||||
fun Long.toSpeedString() = toTrafficString() + "/s"
|
fun Long.toSpeedString() = toTrafficString() + "/s"
|
||||||
|
|
||||||
fun Long.toTrafficString(): String {
|
fun Long.toTrafficString(): String {
|
||||||
|
if (this == 0L)
|
||||||
|
return "\t\t\t0\t B"
|
||||||
|
|
||||||
if (this < threshold)
|
if (this < threshold)
|
||||||
return "$this B"
|
return "${this.toFloat().toShortString()}\t B"
|
||||||
|
|
||||||
val kib = this / divisor
|
val kib = this / divisor
|
||||||
if (kib < threshold)
|
if (kib < threshold)
|
||||||
return "${kib.toShortString()} KB"
|
return "${kib.toShortString()}\t KB"
|
||||||
|
|
||||||
val mib = kib / divisor
|
val mib = kib / divisor
|
||||||
if (mib < threshold)
|
if (mib < threshold)
|
||||||
return "${mib.toShortString()} MB"
|
return "${mib.toShortString()}\t MB"
|
||||||
|
|
||||||
val gib = mib / divisor
|
val gib = mib / divisor
|
||||||
if (gib < threshold)
|
if (gib < threshold)
|
||||||
return "${gib.toShortString()} GB"
|
return "${gib.toShortString()}\t GB"
|
||||||
|
|
||||||
val tib = gib / divisor
|
val tib = gib / divisor
|
||||||
if (tib < threshold)
|
if (tib < threshold)
|
||||||
return "${tib.toShortString()} TB"
|
return "${tib.toShortString()}\t TB"
|
||||||
|
|
||||||
val pib = tib / divisor
|
val pib = tib / divisor
|
||||||
if (pib < threshold)
|
if (pib < threshold)
|
||||||
return "${pib.toShortString()} PB"
|
return "${pib.toShortString()}\t PB"
|
||||||
|
|
||||||
return "∞"
|
return "∞"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Float.toShortString(): String {
|
private fun Float.toShortString(): String {
|
||||||
val s = toString()
|
val s = "%.2f".format(this)
|
||||||
if (s.length <= 4)
|
if (s.length <= 4)
|
||||||
return s
|
return s
|
||||||
return s.substring(0, 4).removeSuffix(".")
|
return s.substring(0, 4).removeSuffix(".")
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.v2ray.ang.extension
|
|
||||||
|
|
||||||
import android.preference.Preference
|
|
||||||
|
|
||||||
fun Preference.onClick(listener: () -> Unit) {
|
|
||||||
setOnPreferenceClickListener {
|
|
||||||
listener()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,11 +5,16 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import com.google.zxing.WriterException
|
import com.google.zxing.WriterException
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
class TaskerReceiver : BroadcastReceiver() {
|
class TaskerReceiver : BroadcastReceiver() {
|
||||||
|
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -21,9 +26,10 @@ class TaskerReceiver : BroadcastReceiver() {
|
|||||||
return
|
return
|
||||||
} else if (switch) {
|
} else if (switch) {
|
||||||
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
||||||
Utils.startVService(context)
|
Utils.startVServiceFromToggle(context)
|
||||||
} else {
|
} else {
|
||||||
Utils.startVService(context, guid)
|
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||||
|
V2RayServiceManager.startV2Ray(context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Utils.stopVService(context)
|
Utils.stopVService(context)
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package com.v2ray.ang.receiver
|
|||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.appwidget.AppWidgetManager
|
import android.appwidget.AppWidgetManager
|
||||||
import android.appwidget.AppWidgetProvider
|
import android.appwidget.AppWidgetProvider
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import org.jetbrains.anko.toast
|
|
||||||
|
|
||||||
class WidgetProvider : AppWidgetProvider() {
|
class WidgetProvider : AppWidgetProvider() {
|
||||||
/**
|
/**
|
||||||
@@ -18,11 +18,20 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
*/
|
*/
|
||||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
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 remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
|
||||||
val intent = Intent(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
|
val intent = Intent(context, WidgetProvider::class.java)
|
||||||
|
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
|
||||||
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
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) {
|
for (appWidgetId in appWidgetIds) {
|
||||||
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
|
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
|
||||||
@@ -30,21 +39,29 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 接收窗口小部件点击时发送的广播
|
* 接收窗口小部件发送的广播
|
||||||
*/
|
*/
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
super.onReceive(context, intent)
|
super.onReceive(context, intent)
|
||||||
if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) {
|
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)
|
Utils.stopVService(context)
|
||||||
} else {
|
} else {
|
||||||
// context.toast(R.string.toast_services_start)
|
Utils.startVServiceFromToggle(context)
|
||||||
Utils.startVService(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,15 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.net.VpnService
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.service.quicksettings.Tile
|
import android.service.quicksettings.Tile
|
||||||
import android.service.quicksettings.TileService
|
import android.service.quicksettings.TileService
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
|
||||||
import com.v2ray.ang.util.MessageUtil
|
import com.v2ray.ang.util.MessageUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import org.jetbrains.anko.toast
|
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
class QSTileService : TileService() {
|
class QSTileService : TileService() {
|
||||||
|
|
||||||
@@ -29,11 +25,10 @@ class QSTileService : TileService() {
|
|||||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
|
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
|
||||||
} else if (state == Tile.STATE_ACTIVE) {
|
} else if (state == Tile.STATE_ACTIVE) {
|
||||||
qsTile?.state = Tile.STATE_ACTIVE
|
qsTile?.state = Tile.STATE_ACTIVE
|
||||||
qsTile?.label = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
|
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
|
||||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
|
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
qsTile?.updateTile()
|
qsTile?.updateTile()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,11 +51,7 @@ class QSTileService : TileService() {
|
|||||||
super.onClick()
|
super.onClick()
|
||||||
when (qsTile.state) {
|
when (qsTile.state) {
|
||||||
Tile.STATE_INACTIVE -> {
|
Tile.STATE_INACTIVE -> {
|
||||||
val intent = VpnService.prepare(this)
|
Utils.startVServiceFromToggle(this)
|
||||||
if (intent == null)
|
|
||||||
if (!Utils.startVService(this)) {
|
|
||||||
toast(R.string.app_tile_first_use)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Tile.STATE_ACTIVE -> {
|
Tile.STATE_ACTIVE -> {
|
||||||
Utils.stopVService(this)
|
Utils.stopVService(this)
|
||||||
@@ -93,4 +84,4 @@ class QSTileService : TileService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
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,69 +1,30 @@
|
|||||||
package com.v2ray.ang.service
|
package com.v2ray.ang.service
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.*
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Color
|
|
||||||
import android.net.*
|
import android.net.*
|
||||||
import android.net.VpnService
|
import android.os.Build
|
||||||
import android.os.*
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.os.StrictMode
|
||||||
import android.support.annotation.RequiresApi
|
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
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.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 com.v2ray.ang.util.Utils
|
||||||
import libv2ray.Libv2ray
|
import kotlinx.coroutines.Dispatchers
|
||||||
import libv2ray.V2RayVPNServiceSupportsSet
|
import kotlinx.coroutines.GlobalScope
|
||||||
import rx.Observable
|
import kotlinx.coroutines.launch
|
||||||
import rx.Subscription
|
|
||||||
import java.net.InetAddress
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileDescriptor
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
import android.os.Build
|
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.util.Log
|
|
||||||
import go.Seq
|
|
||||||
import org.jetbrains.anko.doAsync
|
|
||||||
|
|
||||||
class V2RayVpnService : VpnService() {
|
class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
companion object {
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
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
|
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
|
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
|
||||||
@@ -74,36 +35,38 @@ class V2RayVpnService : VpnService() {
|
|||||||
*
|
*
|
||||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
|
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
|
||||||
*/
|
*/
|
||||||
@TargetApi(28)
|
@delegate:RequiresApi(Build.VERSION_CODES.P)
|
||||||
private val defaultNetworkRequest = NetworkRequest.Builder()
|
private val defaultNetworkRequest by lazy {
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
NetworkRequest.Builder()
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
.build()
|
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
|
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
|
||||||
@TargetApi(28)
|
|
||||||
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
|
@delegate:RequiresApi(Build.VERSION_CODES.P)
|
||||||
override fun onAvailable(network: Network) {
|
private val defaultNetworkCallback by lazy {
|
||||||
setUnderlyingNetworks(arrayOf(network))
|
object : ConnectivityManager.NetworkCallback() {
|
||||||
}
|
override fun onAvailable(network: Network) {
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
|
setUnderlyingNetworks(arrayOf(network))
|
||||||
// it's a good idea to refresh capabilities
|
}
|
||||||
setUnderlyingNetworks(arrayOf(network))
|
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
}
|
// it's a good idea to refresh capabilities
|
||||||
override fun onLost(network: Network) {
|
setUnderlyingNetworks(arrayOf(network))
|
||||||
setUnderlyingNetworks(null)
|
}
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
setUnderlyingNetworks(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private var listeningForDefaultNetwork = false
|
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
|
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
|
||||||
StrictMode.setThreadPolicy(policy)
|
StrictMode.setThreadPolicy(policy)
|
||||||
v2rayPoint.packageName = Utils.packagePath(applicationContext)
|
V2RayServiceManager.serviceControl = SoftReference(this)
|
||||||
Seq.setContext(applicationContext)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRevoke() {
|
override fun onRevoke() {
|
||||||
@@ -117,12 +80,12 @@ class V2RayVpnService : VpnService() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
cancelNotification()
|
stopV2Ray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setup(parameters: String) {
|
private fun setup(parameters: String) {
|
||||||
|
|
||||||
val prepare = VpnService.prepare(this)
|
val prepare = prepare(this)
|
||||||
if (prepare != null) {
|
if (prepare != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -130,7 +93,8 @@ class V2RayVpnService : VpnService() {
|
|||||||
// If the old interface has exactly the same parameters, use it!
|
// If the old interface has exactly the same parameters, use it!
|
||||||
// Configure a builder while parsing the parameters.
|
// Configure a builder while parsing the parameters.
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
|
||||||
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||||
|
|
||||||
parameters.split(" ")
|
parameters.split(" ")
|
||||||
.map { it.split(",") }
|
.map { it.split(",") }
|
||||||
@@ -139,24 +103,39 @@ class V2RayVpnService : VpnService() {
|
|||||||
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
|
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
|
||||||
's' -> builder.addSearchDomain(it[1])
|
's' -> builder.addSearchDomain(it[1])
|
||||||
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
|
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
|
||||||
'r' -> builder.addRoute(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]))
|
||||||
|
}
|
||||||
|
}
|
||||||
'd' -> builder.addDnsServer(it[1])
|
'd' -> builder.addDnsServer(it[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!enableLocalDns) {
|
if(!enableLocalDns) {
|
||||||
Utils.getRemoteDnsServers(defaultDPreference)
|
Utils.getVpnDnsServers()
|
||||||
.forEach {
|
.forEach {
|
||||||
builder.addDnsServer(it)
|
if (Utils.isPureIpAddress(it)) {
|
||||||
}
|
builder.addDnsServer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
|
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
||||||
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
|
settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
|
||||||
val apps = defaultDPreference.getPrefStringSet(PerAppProxyActivity.PREF_PER_APP_PROXY_SET, null)
|
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||||
val bypassApps = defaultDPreference.getPrefBoolean(PerAppProxyActivity.PREF_BYPASS_APPS, false)
|
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
|
||||||
apps?.forEach {
|
apps?.forEach {
|
||||||
try {
|
try {
|
||||||
if (bypassApps)
|
if (bypassApps)
|
||||||
@@ -173,33 +152,42 @@ class V2RayVpnService : VpnService() {
|
|||||||
try {
|
try {
|
||||||
mInterface.close()
|
mInterface.close()
|
||||||
} catch (ignored: Exception) {
|
} 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 >= 28) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
builder.setMetered(false)
|
||||||
listeningForDefaultNetwork = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new interface using the builder and save the parameters.
|
// Create a new interface using the builder and save the parameters.
|
||||||
mInterface = builder.establish()
|
try {
|
||||||
|
mInterface = builder.establish()!!
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// non-nullable lateinit var
|
||||||
|
e.printStackTrace()
|
||||||
|
stopV2Ray()
|
||||||
|
}
|
||||||
|
|
||||||
sendFd()
|
sendFd()
|
||||||
startSpeedNotification()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shutdown() {
|
private fun sendFd() {
|
||||||
stopV2Ray(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendFd() {
|
|
||||||
val fd = mInterface.fileDescriptor
|
val fd = mInterface.fileDescriptor
|
||||||
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
|
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
|
||||||
|
|
||||||
doAsync {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var tries = 0
|
var tries = 0
|
||||||
while (true) try {
|
while (true) try {
|
||||||
Thread.sleep(50L shl tries)
|
Thread.sleep(1000L shl tries)
|
||||||
Log.d(packageName, "sendFd tries: " + tries.toString())
|
Log.d(packageName, "sendFd tries: $tries")
|
||||||
LocalSocket().use { localSocket ->
|
LocalSocket().use { localSocket ->
|
||||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||||
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
||||||
@@ -215,74 +203,27 @@ class V2RayVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
startV2ray()
|
V2RayServiceManager.startV2rayPoint()
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
//return super.onStartCommand(intent, flags, startId)
|
//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) {
|
private fun stopV2Ray(isForced: Boolean = true) {
|
||||||
// val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "")
|
// val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "")
|
||||||
// val emptyInfo = VpnNetworkInfo()
|
// val emptyInfo = VpnNetworkInfo()
|
||||||
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
||||||
// saveVpnNetworkInfo(configName, info)
|
// saveVpnNetworkInfo(configName, info)
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
if (listeningForDefaultNetwork) {
|
|
||||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
|
||||||
listeningForDefaultNetwork = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (v2rayPoint.isRunning) {
|
|
||||||
try {
|
try {
|
||||||
v2rayPoint.stopLoop()
|
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||||
} catch (e: Exception) {
|
} catch (ignored: Exception) {
|
||||||
Log.d(packageName, e.toString())
|
// ignored
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "")
|
V2RayServiceManager.stopV2rayPoint()
|
||||||
cancelNotification()
|
|
||||||
|
|
||||||
if (isForced) {
|
if (isForced) {
|
||||||
try {
|
|
||||||
unregisterReceiver(mMsgReceive)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
|
|
||||||
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
|
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
|
||||||
//It's strage but true.
|
//It's strage but true.
|
||||||
//This can be verified by putting stopself() behind and call stopLoop and startLoop
|
//This can be verified by putting stopself() behind and call stopLoop and startLoop
|
||||||
@@ -293,207 +234,26 @@ class V2RayVpnService : VpnService() {
|
|||||||
try {
|
try {
|
||||||
mInterface.close()
|
mInterface.close()
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNotification() {
|
override fun getService(): Service {
|
||||||
val startMainIntent = Intent(applicationContext, MainActivity::class.java)
|
return this
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
override fun startService(parameters: String) {
|
||||||
private fun createNotificationChannel(): String {
|
setup(parameters)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelNotification() {
|
override fun stopService() {
|
||||||
stopForeground(true)
|
stopV2Ray(true)
|
||||||
mBuilder = null
|
|
||||||
mSubscription?.unsubscribe()
|
|
||||||
mSubscription = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNotification(contentText: String) {
|
override fun vpnProtect(socket: Int): Boolean {
|
||||||
if (mBuilder != null) {
|
return protect(socket)
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,206 +0,0 @@
|
|||||||
package com.v2ray.ang.ui
|
|
||||||
|
|
||||||
import android.app.ActivityOptions
|
|
||||||
import android.app.FragmentManager
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.support.design.widget.NavigationView
|
|
||||||
import android.support.v4.view.GravityCompat
|
|
||||||
import android.support.v4.widget.DrawerLayout
|
|
||||||
import android.support.v7.app.ActionBarDrawerToggle
|
|
||||||
import android.support.v7.widget.Toolbar
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
//import com.v2ray.ang.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,17 +1,19 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.android.synthetic.main.activity_logcat.*
|
import kotlinx.android.synthetic.main.activity_logcat.*
|
||||||
import org.jetbrains.anko.doAsync
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.anko.toast
|
import kotlinx.coroutines.GlobalScope
|
||||||
import org.jetbrains.anko.uiThread
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.LinkedHashSet
|
import java.util.LinkedHashSet
|
||||||
|
|
||||||
@@ -24,15 +26,22 @@ class LogcatActivity : BaseActivity() {
|
|||||||
title = getString(R.string.title_logcat)
|
title = getString(R.string.title_logcat)
|
||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
logcat()
|
logcat(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logcat() {
|
private fun logcat(shouldFlushLog: Boolean) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pb_waiting.visibility = View.VISIBLE
|
pb_waiting.visibility = View.VISIBLE
|
||||||
|
|
||||||
doAsync {
|
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()
|
||||||
|
}
|
||||||
val lst = LinkedHashSet<String>()
|
val lst = LinkedHashSet<String>()
|
||||||
lst.add("logcat")
|
lst.add("logcat")
|
||||||
lst.add("-d")
|
lst.add("-d")
|
||||||
@@ -45,13 +54,15 @@ class LogcatActivity : BaseActivity() {
|
|||||||
// InputStreamReader(process.inputStream))
|
// InputStreamReader(process.inputStream))
|
||||||
// val allText = bufferedReader.use(BufferedReader::readText)
|
// val allText = bufferedReader.use(BufferedReader::readText)
|
||||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||||
uiThread {
|
launch(Dispatchers.Main) {
|
||||||
tv_logcat.text = allText
|
tv_logcat.text = allText
|
||||||
tv_logcat.movementMethod = ScrollingMovementMethod()
|
tv_logcat.movementMethod = ScrollingMovementMethod()
|
||||||
pb_waiting.visibility = View.GONE
|
pb_waiting.visibility = View.GONE
|
||||||
|
Handler(Looper.getMainLooper()).post { sv_logcat.fullScroll(View.FOCUS_DOWN) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +77,10 @@ class LogcatActivity : BaseActivity() {
|
|||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.delete -> {
|
||||||
|
logcat(true)
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,45 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.*
|
import android.arch.lifecycle.ViewModelProviders
|
||||||
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
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.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.design.widget.NavigationView
|
||||||
import android.support.v4.view.GravityCompat
|
import android.support.v4.view.GravityCompat
|
||||||
import android.support.v7.app.ActionBarDrawerToggle
|
import android.support.v7.app.ActionBarDrawerToggle
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.helper.ItemTouchHelper
|
import android.support.v7.widget.helper.ItemTouchHelper
|
||||||
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
//import com.v2ray.ang.InappBuyActivity
|
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 rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import java.net.URL
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
|
||||||
import com.v2ray.ang.util.AngConfigManager.configs
|
|
||||||
|
|
||||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -42,22 +49,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
private const val REQUEST_SCAN_URL = 3
|
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 adapter by lazy { MainRecyclerAdapter(this) }
|
||||||
|
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||||
|
val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -66,28 +62,23 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
|
|
||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
if (isRunning) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
Utils.stopVService(this)
|
Utils.stopVService(this)
|
||||||
} else {
|
} else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
|
||||||
val intent = VpnService.prepare(this)
|
val intent = VpnService.prepare(this)
|
||||||
if (intent == null) {
|
if (intent == null) {
|
||||||
startV2Ray()
|
startV2Ray()
|
||||||
} else {
|
} else {
|
||||||
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
|
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
startV2Ray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layout_test.setOnClickListener {
|
layout_test.setOnClickListener {
|
||||||
if (isRunning) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
|
||||||
|
|
||||||
tv_test_state.text = getString(R.string.connection_test_testing)
|
tv_test_state.text = getString(R.string.connection_test_testing)
|
||||||
doAsync {
|
mainViewModel.testCurrentServerRealPing()
|
||||||
val result = Utils.testConnection(this@MainActivity, socksPort)
|
|
||||||
uiThread {
|
|
||||||
tv_test_state.text = Utils.getEditable(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// tv_test_state.text = getString(R.string.connection_test_fail)
|
// tv_test_state.text = getString(R.string.connection_test_fail)
|
||||||
}
|
}
|
||||||
@@ -107,43 +98,68 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
drawer_layout.addDrawerListener(toggle)
|
drawer_layout.addDrawerListener(toggle)
|
||||||
toggle.syncState()
|
toggle.syncState()
|
||||||
nav_view.setNavigationItemSelectedListener(this)
|
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() {
|
fun startV2Ray() {
|
||||||
if (AngConfigManager.configs.index < 0) {
|
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
showCircle()
|
showCircle()
|
||||||
// toast(R.string.toast_services_start)
|
// toast(R.string.toast_services_start)
|
||||||
if (!Utils.startVService(this)) {
|
V2RayServiceManager.startV2Ray(this)
|
||||||
hideCircle()
|
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() {
|
public override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
adapter.updateConfigList()
|
mainViewModel.reloadServerList()
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun onPause() {
|
public override fun onPause() {
|
||||||
@@ -162,8 +178,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
|
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
|
||||||
}
|
}
|
||||||
REQUEST_FILE_CHOOSER -> {
|
REQUEST_FILE_CHOOSER -> {
|
||||||
if (resultCode == RESULT_OK) {
|
val uri = data?.data
|
||||||
val uri = data!!.data
|
if (resultCode == RESULT_OK && uri != null) {
|
||||||
readContentFromUri(uri)
|
readContentFromUri(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,18 +205,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_manually_vmess -> {
|
R.id.import_manually_vmess -> {
|
||||||
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning)
|
startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
|
||||||
adapter.updateConfigList()
|
setClass(this, ServerActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_manually_ss -> {
|
R.id.import_manually_ss -> {
|
||||||
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning)
|
startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
|
||||||
adapter.updateConfigList()
|
setClass(this, ServerActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_manually_socks -> {
|
R.id.import_manually_socks -> {
|
||||||
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning)
|
startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
|
||||||
adapter.updateConfigList()
|
setClass(this, ServerActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_config_custom_clipboard -> {
|
R.id.import_config_custom_clipboard -> {
|
||||||
@@ -231,8 +247,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.export_all -> {
|
R.id.export_all -> {
|
||||||
if (AngConfigManager.shareAll2Clipboard() == 0) {
|
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
|
||||||
//remove toast, otherwise it will block previous warning message
|
toast(R.string.toast_success)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
@@ -240,20 +256,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.ping_all -> {
|
R.id.ping_all -> {
|
||||||
for (k in 0 until configs.vmess.count()) {
|
mainViewModel.testAllTcping()
|
||||||
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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +285,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
.request(Manifest.permission.CAMERA)
|
.request(Manifest.permission.CAMERA)
|
||||||
.subscribe {
|
.subscribe {
|
||||||
if (it)
|
if (it)
|
||||||
startActivityForResult<ScannerActivity>(requestCode)
|
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
|
||||||
else
|
else
|
||||||
toast(R.string.toast_permission_denied)
|
toast(R.string.toast_permission_denied)
|
||||||
}
|
}
|
||||||
@@ -306,10 +309,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun importBatchConfig(server: String?, subid: String = "") {
|
fun importBatchConfig(server: String?, subid: String = "") {
|
||||||
val count = AngConfigManager.importBatchConfig(server, subid)
|
var count = AngConfigManager.importBatchConfig(server, subid)
|
||||||
|
if (count <= 0) {
|
||||||
|
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid)
|
||||||
|
}
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
adapter.updateConfigList()
|
mainViewModel.reloadServerList()
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
@@ -368,9 +374,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
toast(R.string.toast_invalid_url)
|
toast(R.string.toast_invalid_url)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
doAsync {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val configText = URL(url).readText()
|
val configText = try {
|
||||||
uiThread {
|
URL(url).readText()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
importCustomizeConfig(configText)
|
importCustomizeConfig(configText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,24 +399,27 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
: Boolean {
|
: Boolean {
|
||||||
try {
|
try {
|
||||||
toast(R.string.title_sub_update)
|
toast(R.string.title_sub_update)
|
||||||
val subItem = AngConfigManager.configs.subItem
|
MmkvManager.decodeSubscriptions().forEach {
|
||||||
for (k in 0 until subItem.count()) {
|
if (TextUtils.isEmpty(it.first)
|
||||||
if (TextUtils.isEmpty(subItem[k].id)
|
|| TextUtils.isEmpty(it.second.remarks)
|
||||||
|| TextUtils.isEmpty(subItem[k].remarks)
|
|| TextUtils.isEmpty(it.second.url)
|
||||||
|| TextUtils.isEmpty(subItem[k].url)
|
|
||||||
) {
|
) {
|
||||||
continue
|
return@forEach
|
||||||
}
|
}
|
||||||
val id = subItem[k].id
|
val url = it.second.url
|
||||||
val url = subItem[k].url
|
|
||||||
if (!Utils.isValidUrl(url)) {
|
if (!Utils.isValidUrl(url)) {
|
||||||
continue
|
return@forEach
|
||||||
}
|
}
|
||||||
Log.d("Main", url)
|
Log.d("Main", url)
|
||||||
doAsync {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val configText = URL(url).readText()
|
val configText = try {
|
||||||
uiThread {
|
URL(url).readText()
|
||||||
importBatchConfig(Utils.decode(configText), id)
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
importBatchConfig(Utils.decode(configText), it.first)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,9 +456,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
.subscribe {
|
.subscribe {
|
||||||
if (it) {
|
if (it) {
|
||||||
try {
|
try {
|
||||||
val inputStream = contentResolver.openInputStream(uri)
|
contentResolver.openInputStream(uri).use { input ->
|
||||||
val configText = inputStream.bufferedReader().readText()
|
importCustomizeConfig(input?.bufferedReader()?.readText())
|
||||||
importCustomizeConfig(configText)
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -457,19 +471,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
* import customize config
|
* import customize config
|
||||||
*/
|
*/
|
||||||
fun importCustomizeConfig(server: String?) {
|
fun importCustomizeConfig(server: String?) {
|
||||||
if (server == null) {
|
try {
|
||||||
return
|
if (server == null || TextUtils.isEmpty(server)) {
|
||||||
}
|
toast(R.string.toast_none_data)
|
||||||
if (!V2rayConfigUtil.isValidConfig(server)) {
|
return
|
||||||
toast(R.string.toast_config_file_invalid)
|
}
|
||||||
return
|
mainViewModel.appendCustomConfigServer(server)
|
||||||
}
|
|
||||||
val resId = AngConfigManager.importCustomizeConfig(server)
|
|
||||||
if (resId > 0) {
|
|
||||||
toast(resId)
|
|
||||||
} else {
|
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
adapter.updateConfigList()
|
adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||||
|
e.printStackTrace()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -482,35 +495,6 @@ 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 {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
moveTaskToBack(false)
|
moveTaskToBack(false)
|
||||||
@@ -549,10 +533,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
//R.id.server_profile -> activityClass = MainActivity::class.java
|
//R.id.server_profile -> activityClass = MainActivity::class.java
|
||||||
R.id.sub_setting -> {
|
R.id.sub_setting -> {
|
||||||
startActivity<SubSettingActivity>()
|
startActivity(Intent(this, SubSettingActivity::class.java))
|
||||||
}
|
}
|
||||||
R.id.settings -> {
|
R.id.settings -> {
|
||||||
startActivity<SettingsActivity>("isRunning" to isRunning)
|
startActivity(Intent(this, SettingsActivity::class.java)
|
||||||
|
.putExtra("isRunning", mainViewModel.isRunning.value == true))
|
||||||
}
|
}
|
||||||
R.id.feedback -> {
|
R.id.feedback -> {
|
||||||
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
||||||
@@ -564,10 +549,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
// startActivity<InappBuyActivity>()
|
// startActivity<InappBuyActivity>()
|
||||||
}
|
}
|
||||||
R.id.logcat -> {
|
R.id.logcat -> {
|
||||||
startActivity<LogcatActivity>()
|
startActivity(Intent(this, LogcatActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drawer_layout.closeDrawer(GravityCompat.START)
|
drawer_layout.closeDrawer(GravityCompat.START)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,31 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.text.TextUtils
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.AngConfig
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||||
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.android.synthetic.main.item_qrcode.view.*
|
import kotlinx.android.synthetic.main.item_qrcode.view.*
|
||||||
import kotlinx.android.synthetic.main.item_recycler_main.view.*
|
import kotlinx.android.synthetic.main.item_recycler_main.view.*
|
||||||
import org.jetbrains.anko.*
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
|
||||||
|
|
||||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
|
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
|
||||||
, ItemTouchHelperAdapter {
|
, ItemTouchHelperAdapter {
|
||||||
@@ -28,140 +35,112 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var mActivity: MainActivity = activity
|
private var mActivity: MainActivity = activity
|
||||||
private lateinit var configs: AngConfig
|
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val share_method: Array<out String> by lazy {
|
private val share_method: Array<out String> by lazy {
|
||||||
mActivity.resources.getStringArray(R.array.share_method)
|
mActivity.resources.getStringArray(R.array.share_method)
|
||||||
}
|
}
|
||||||
|
var isRunning = false
|
||||||
|
|
||||||
var changeable: Boolean = true
|
override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
|
||||||
set(value) {
|
|
||||||
if (field == value)
|
|
||||||
return
|
|
||||||
field = value
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
updateConfigList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = configs.vmess.count() + 1
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||||
if (holder is MainViewHolder) {
|
if (holder is MainViewHolder) {
|
||||||
val configType = configs.vmess[position].configType
|
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
||||||
val remarks = configs.vmess[position].remarks
|
val config = mActivity.mainViewModel.serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?: return
|
||||||
val subid = configs.vmess[position].subid
|
val outbound = config.getProxyOutbound()
|
||||||
val address = configs.vmess[position].address
|
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||||
val port = configs.vmess[position].port
|
|
||||||
val test_result = configs.vmess[position].testResult
|
|
||||||
|
|
||||||
holder.name.text = remarks
|
holder.name.text = config.remarks
|
||||||
holder.radio.isChecked = (position == configs.index)
|
holder.radio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||||
holder.itemView.backgroundColor = Color.TRANSPARENT
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
holder.test_result.text = test_result
|
holder.test_result.text = aff?.getTestDelayString() ?: ""
|
||||||
|
if (aff?.testDelayMillis?:0L < 0L) {
|
||||||
if (TextUtils.isEmpty(subid)) {
|
holder.test_result.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
|
||||||
holder.subid.text = ""
|
|
||||||
} else {
|
} else {
|
||||||
holder.subid.text = "S"
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configType == AppConfig.EConfigType.Vmess) {
|
var shareOptions = share_method.asList()
|
||||||
holder.type.text = "vmess"
|
if (config.configType == EConfigType.CUSTOM) {
|
||||||
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)
|
holder.type.text = mActivity.getString(R.string.server_customize_config)
|
||||||
holder.statistics.text = ""//mActivity.getString(R.string.server_customize_config)
|
shareOptions = shareOptions.takeLast(1)
|
||||||
holder.layout_share.visibility = View.INVISIBLE
|
} else if (config.configType == EConfigType.VLESS) {
|
||||||
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
|
holder.type.text = config.configType.name
|
||||||
holder.type.text = "shadowsocks"
|
} else {
|
||||||
holder.statistics.text = "$address : $port"
|
holder.type.text = config.configType.name.toLowerCase()
|
||||||
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 {
|
holder.layout_share.setOnClickListener {
|
||||||
mActivity.selector(null, share_method.asList()) { dialogInterface, i ->
|
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||||
try {
|
try {
|
||||||
when (i) {
|
when (i) {
|
||||||
0 -> {
|
0 -> {
|
||||||
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
|
if (config.configType == EConfigType.CUSTOM) {
|
||||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
|
shareFullContent(guid)
|
||||||
|
} else {
|
||||||
mActivity.alert {
|
val iv = LayoutInflater.from(mActivity).inflate(R.layout.item_qrcode, null)
|
||||||
customView {
|
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
|
||||||
linearLayout {
|
AlertDialog.Builder(mActivity).setView(iv).show()
|
||||||
addView(iv)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
if (AngConfigManager.share2Clipboard(position) == 0) {
|
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
||||||
mActivity.toast(R.string.toast_success)
|
mActivity.toast(R.string.toast_success)
|
||||||
} else {
|
} else {
|
||||||
mActivity.toast(R.string.toast_failure)
|
mActivity.toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> shareFullContent(guid)
|
||||||
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
|
else -> mActivity.toast("else")
|
||||||
mActivity.toast(R.string.toast_success)
|
|
||||||
} else {
|
|
||||||
mActivity.toast(R.string.toast_failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else ->
|
|
||||||
mActivity.toast("else")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.layout_edit.setOnClickListener {
|
holder.layout_edit.setOnClickListener {
|
||||||
if (configType == AppConfig.EConfigType.Vmess) {
|
val intent = Intent().putExtra("guid", guid)
|
||||||
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable)
|
.putExtra("isRunning", isRunning)
|
||||||
} else if (configType == AppConfig.EConfigType.Custom) {
|
if (config.configType == EConfigType.CUSTOM) {
|
||||||
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
|
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
||||||
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
|
} else {
|
||||||
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable)
|
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
||||||
} else if (configType == AppConfig.EConfigType.Socks) {
|
|
||||||
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
holder.layout_remove.setOnClickListener {
|
holder.layout_remove.setOnClickListener {
|
||||||
if (configs.index != position) {
|
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
if (AngConfigManager.removeServer(position) == 0) {
|
mActivity.mainViewModel.removeServer(guid)
|
||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
updateSelectedItem(position)
|
notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.infoContainer.setOnClickListener {
|
holder.infoContainer.setOnClickListener {
|
||||||
if (changeable) {
|
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||||
AngConfigManager.setActiveServer(position)
|
if (guid != selected) {
|
||||||
} else {
|
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||||
mActivity.showCircle()
|
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
|
||||||
Utils.stopVService(mActivity)
|
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
|
||||||
AngConfigManager.setActiveServer(position)
|
if (isRunning) {
|
||||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
mActivity.showCircle()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
Utils.stopVService(mActivity)
|
||||||
.subscribe {
|
Observable.timer(500, TimeUnit.MILLISECONDS)
|
||||||
mActivity.showCircle()
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
if (!Utils.startVService(mActivity)) {
|
.subscribe {
|
||||||
|
V2RayServiceManager.startV2Ray(mActivity)
|
||||||
mActivity.hideCircle()
|
mActivity.hideCircle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (holder is FooterViewHolder) {
|
if (holder is FooterViewHolder) {
|
||||||
@@ -170,49 +149,43 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
holder.layout_edit.visibility = View.INVISIBLE
|
holder.layout_edit.visibility = View.INVISIBLE
|
||||||
} else {
|
} else {
|
||||||
holder.layout_edit.setOnClickListener {
|
holder.layout_edit.setOnClickListener {
|
||||||
Utils.openUri(mActivity, AppConfig.promotionUrl)
|
Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shareFullContent(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 {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||||
when (viewType) {
|
return when (viewType) {
|
||||||
VIEW_TYPE_ITEM ->
|
VIEW_TYPE_ITEM ->
|
||||||
return MainViewHolder(parent.context.layoutInflater
|
MainViewHolder(LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.item_recycler_main, parent, false))
|
.inflate(R.layout.item_recycler_main, parent, false))
|
||||||
else ->
|
else ->
|
||||||
return FooterViewHolder(parent.context.layoutInflater
|
FooterViewHolder(LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.item_recycler_footer, parent, false))
|
.inflate(R.layout.item_recycler_footer, parent, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConfigList() {
|
|
||||||
configs = AngConfigManager.configs
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun updateSelectedItem() {
|
|
||||||
// updateSelectedItem(configs.index)
|
|
||||||
// }
|
|
||||||
|
|
||||||
fun updateSelectedItem(pos: Int) {
|
|
||||||
//notifyItemChanged(pos)
|
|
||||||
notifyItemRangeChanged(pos, itemCount - pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
if (position == configs.vmess.count()) {
|
return if (position == mActivity.mainViewModel.serverList.size) {
|
||||||
return VIEW_TYPE_FOOTER
|
VIEW_TYPE_FOOTER
|
||||||
} else {
|
} else {
|
||||||
return VIEW_TYPE_ITEM
|
VIEW_TYPE_ITEM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||||
|
|
||||||
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
|
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
|
||||||
val subid = itemView.tv_subid
|
val subscription = itemView.tv_subscription!!
|
||||||
val radio = itemView.btn_radio!!
|
val radio = itemView.btn_radio!!
|
||||||
val name = itemView.tv_name!!
|
val name = itemView.tv_name!!
|
||||||
val test_result = itemView.tv_test_result!!
|
val test_result = itemView.tv_test_result!!
|
||||||
@@ -245,24 +218,26 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemDismiss(position: Int) {
|
override fun onItemDismiss(position: Int) {
|
||||||
if (configs.index != position) {
|
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
||||||
|
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
// mActivity.alert(R.string.del_config_comfirm) {
|
// mActivity.alert(R.string.del_config_comfirm) {
|
||||||
// positiveButton(android.R.string.ok) {
|
// positiveButton(android.R.string.ok) {
|
||||||
if (AngConfigManager.removeServer(position) == 0) {
|
mActivity.mainViewModel.removeServer(guid)
|
||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
}
|
|
||||||
// }
|
// }
|
||||||
// show()
|
// show()
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
updateSelectedItem(position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||||
AngConfigManager.swapServer(fromPosition, toPosition)
|
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
|
||||||
notifyItemMoved(fromPosition, toPosition)
|
notifyItemMoved(fromPosition, toPosition)
|
||||||
//notifyDataSetChanged()
|
//notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
|
||||||
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onItemMoveCompleted() {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.animation.Animator
|
|||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v7.preference.PreferenceManager
|
||||||
|
import android.support.v7.widget.DividerItemDecoration
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -12,9 +15,7 @@ import android.view.MenuItem
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.animation.AccelerateInterpolator
|
import android.view.animation.AccelerateInterpolator
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import com.dinuscxj.itemdecoration.LinearDividerItemDecoration
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
|
||||||
import com.v2ray.ang.util.AppManagerUtil
|
import com.v2ray.ang.util.AppManagerUtil
|
||||||
import kotlinx.android.synthetic.main.activity_bypass_list.*
|
import kotlinx.android.synthetic.main.activity_bypass_list.*
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
@@ -25,21 +26,19 @@ import android.view.inputmethod.EditorInfo
|
|||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.AppInfo
|
import com.v2ray.ang.dto.AppInfo
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.v2RayApplication
|
import com.v2ray.ang.extension.v2RayApplication
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import org.jetbrains.anko.doAsync
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.anko.toast
|
import kotlinx.coroutines.GlobalScope
|
||||||
import org.jetbrains.anko.uiThread
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
class PerAppProxyActivity : BaseActivity() {
|
class PerAppProxyActivity : BaseActivity() {
|
||||||
companion object {
|
|
||||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
|
||||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var adapter: PerAppProxyAdapter? = null
|
private var adapter: PerAppProxyAdapter? = null
|
||||||
private var appsAll: List<AppInfo>? = null
|
private var appsAll: List<AppInfo>? = null
|
||||||
|
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -47,11 +46,10 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
val dividerItemDecoration = LinearDividerItemDecoration(
|
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||||
this, LinearDividerItemDecoration.LINEAR_DIVIDER_VERTICAL)
|
|
||||||
recycler_view.addItemDecoration(dividerItemDecoration)
|
recycler_view.addItemDecoration(dividerItemDecoration)
|
||||||
|
|
||||||
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
|
val blacklist = defaultSharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, null)
|
||||||
|
|
||||||
AppManagerUtil.rxLoadNetworkAppList(this)
|
AppManagerUtil.rxLoadNetworkAppList(this)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
@@ -140,15 +138,15 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
|
switch_per_app_proxy.setOnCheckedChangeListener { _, isChecked ->
|
||||||
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked)
|
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
|
||||||
}
|
}
|
||||||
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)
|
switch_per_app_proxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||||
|
|
||||||
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
|
switch_bypass_apps.setOnCheckedChangeListener { _, isChecked ->
|
||||||
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked)
|
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
|
||||||
}
|
}
|
||||||
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false)
|
switch_bypass_apps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
|
||||||
|
|
||||||
et_search.setOnEditorActionListener { v, actionId, event ->
|
et_search.setOnEditorActionListener { v, actionId, event ->
|
||||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||||
@@ -182,7 +180,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
adapter?.let {
|
adapter?.let {
|
||||||
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist)
|
defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +218,14 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
private fun selectProxyApp() {
|
private fun selectProxyApp() {
|
||||||
toast(R.string.msg_downloading_content)
|
toast(R.string.msg_downloading_content)
|
||||||
val url = AppConfig.androidpackagenamelistUrl
|
val url = AppConfig.androidpackagenamelistUrl
|
||||||
doAsync {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val content = URL(url).readText()
|
val content = try {
|
||||||
uiThread {
|
URL(url).readText()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
Log.d("selectProxyApp", content)
|
Log.d("selectProxyApp", content)
|
||||||
selectProxyApp(content)
|
selectProxyApp(content)
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
@@ -276,4 +279,4 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package com.v2ray.ang.ui
|
|||||||
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.AppInfo
|
import com.v2ray.ang.dto.AppInfo
|
||||||
import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.*
|
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.*
|
import java.util.*
|
||||||
|
|
||||||
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
||||||
@@ -45,7 +43,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
|||||||
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
||||||
// .inflate(R.layout.item_recycler_bypass_list, parent, false))
|
// .inflate(R.layout.item_recycler_bypass_list, parent, false))
|
||||||
|
|
||||||
else -> AppViewHolder(ctx.layoutInflater
|
else -> AppViewHolder(LayoutInflater.from(ctx)
|
||||||
.inflate(R.layout.item_recycler_bypass_list, parent, false))
|
.inflate(R.layout.item_recycler_bypass_list, parent, false))
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -68,17 +66,17 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
|||||||
fun bind(appInfo: AppInfo) {
|
fun bind(appInfo: AppInfo) {
|
||||||
this.appInfo = appInfo
|
this.appInfo = appInfo
|
||||||
|
|
||||||
icon.image = appInfo.appIcon
|
icon.setImageDrawable(appInfo.appIcon)
|
||||||
// name.text = appInfo.appName
|
// name.text = appInfo.appName
|
||||||
|
|
||||||
checkBox.isChecked = inBlacklist
|
checkBox.isChecked = inBlacklist
|
||||||
package_name.text = appInfo.packageName
|
package_name.text = appInfo.packageName
|
||||||
if (appInfo.isSystemApp) {
|
if (appInfo.isSystemApp) {
|
||||||
name.text = String.format("** %1s", appInfo.appName)
|
name.text = String.format("** %1s", appInfo.appName)
|
||||||
name.textColor = Color.RED
|
name.setTextColor(Color.RED)
|
||||||
} else {
|
} else {
|
||||||
name.text = appInfo.appName
|
name.text = appInfo.appName
|
||||||
name.textColor = Color.DKGRAY
|
name.setTextColor(Color.DKGRAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener(this)
|
itemView.setOnClickListener(this)
|
||||||
@@ -94,4 +92,4 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.Fragment
|
import android.support.v4.app.Fragment
|
||||||
import android.text.TextUtils
|
import android.support.v7.preference.PreferenceManager
|
||||||
import android.util.Log
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.android.synthetic.main.fragment_routing_settings.*
|
import kotlinx.android.synthetic.main.fragment_routing_settings.*
|
||||||
import org.jetbrains.anko.toast
|
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import org.jetbrains.anko.doAsync
|
import com.v2ray.ang.extension.toast
|
||||||
import org.jetbrains.anko.startActivityForResult
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.anko.support.v4.startActivityForResult
|
import kotlinx.coroutines.GlobalScope
|
||||||
import org.jetbrains.anko.support.v4.toast
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.anko.uiThread
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
|
||||||
class RoutingSettingsFragment : Fragment() {
|
class RoutingSettingsFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val routing_arg = "routing_arg"
|
private const val routing_arg = "routing_arg"
|
||||||
@@ -32,6 +26,8 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
private const val REQUEST_SCAN_APPEND = 12
|
private const val REQUEST_SCAN_APPEND = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
@@ -46,10 +42,10 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "")
|
val content = defaultSharedPreferences.getString(arguments!!.getString(routing_arg), "")
|
||||||
et_routing_content.text = Utils.getEditable(content!!)
|
et_routing_content.text = Utils.getEditable(content!!)
|
||||||
|
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
@@ -63,7 +59,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.save_routing -> {
|
R.id.save_routing -> {
|
||||||
val content = et_routing_content.text.toString()
|
val content = et_routing_content.text.toString()
|
||||||
activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content)
|
defaultSharedPreferences.edit().putString(arguments!!.getString(routing_arg), content).apply()
|
||||||
activity?.toast(R.string.toast_success)
|
activity?.toast(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -96,7 +92,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
.request(Manifest.permission.CAMERA)
|
.request(Manifest.permission.CAMERA)
|
||||||
.subscribe {
|
.subscribe {
|
||||||
if (it)
|
if (it)
|
||||||
startActivityForResult<ScannerActivity>(requestCode)
|
startActivityForResult(Intent(activity, ScannerActivity::class.java), requestCode)
|
||||||
else
|
else
|
||||||
activity?.toast(R.string.toast_permission_denied)
|
activity?.toast(R.string.toast_permission_denied)
|
||||||
}
|
}
|
||||||
@@ -118,12 +114,17 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast(R.string.msg_downloading_content)
|
activity?.toast(R.string.msg_downloading_content)
|
||||||
doAsync {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
val content = URL(url).readText()
|
val content = try {
|
||||||
uiThread {
|
URL(url).readText()
|
||||||
et_routing_content.text = Utils.getEditable(content!!)
|
} catch (e: Exception) {
|
||||||
toast(R.string.toast_success)
|
e.printStackTrace()
|
||||||
|
""
|
||||||
|
}
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
et_routing_content.text = Utils.getEditable(content)
|
||||||
|
activity?.toast(R.string.toast_success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import com.tbruyelle.rxpermissions.RxPermissions
|
|||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import org.jetbrains.anko.*
|
import com.v2ray.ang.extension.toast
|
||||||
|
|
||||||
class ScScannerActivity : BaseActivity() {
|
class ScScannerActivity : BaseActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -24,7 +24,7 @@ class ScScannerActivity : BaseActivity() {
|
|||||||
.request(Manifest.permission.CAMERA)
|
.request(Manifest.permission.CAMERA)
|
||||||
.subscribe {
|
.subscribe {
|
||||||
if (it)
|
if (it)
|
||||||
startActivityForResult<ScannerActivity>(requestCode)
|
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
|
||||||
else
|
else
|
||||||
toast(R.string.toast_permission_denied)
|
toast(R.string.toast_permission_denied)
|
||||||
}
|
}
|
||||||
@@ -43,10 +43,10 @@ class ScScannerActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
toast(R.string.toast_failure)
|
||||||
}
|
}
|
||||||
startActivity<MainActivity>()
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,104 +1,22 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.content.*
|
|
||||||
import android.net.VpnService
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
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() {
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
moveTaskToBack(true)
|
moveTaskToBack(true)
|
||||||
|
|
||||||
setContentView(R.layout.activity_none)
|
setContentView(R.layout.activity_none)
|
||||||
|
|
||||||
val isRunning = Utils.isServiceRun(this, "com.v2ray.ang.service.V2RayVpnService")
|
if (V2RayServiceManager.v2rayPoint.isRunning) {
|
||||||
if (isRunning) {
|
Utils.stopVService(this)
|
||||||
//Utils.stopVService(this)
|
|
||||||
mMsgReceive = ReceiveMessageHandler(this@ScSwitchActivity)
|
|
||||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
|
||||||
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Utils.startVService(this)
|
Utils.startVServiceFromToggle(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,23 +7,13 @@ import com.google.zxing.Result
|
|||||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.icu.util.TimeUnit
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.QRCodeDecoder
|
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 {
|
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -33,8 +23,8 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
|||||||
|
|
||||||
private var mScannerView: ZXingScannerView? = null
|
private var mScannerView: ZXingScannerView? = null
|
||||||
|
|
||||||
public override fun onCreate(state: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(state)
|
super.onCreate(savedInstanceState)
|
||||||
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
|
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
|
||||||
|
|
||||||
mScannerView?.setAutoFocus(true)
|
mScannerView?.setAutoFocus(true)
|
||||||
@@ -119,10 +109,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
|||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
REQUEST_FILE_CHOOSER ->
|
REQUEST_FILE_CHOOSER -> {
|
||||||
if (resultCode == RESULT_OK) {
|
val uri = data?.data
|
||||||
|
if (resultCode == RESULT_OK && uri != null) {
|
||||||
try {
|
try {
|
||||||
val uri = data!!.data
|
|
||||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||||
finished(text)
|
finished(text)
|
||||||
@@ -131,6 +121,7 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
|||||||
toast(e.message.toString())
|
toast(e.message.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
package com.v2ray.ang.ui
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextUtils
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuItem
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.v2ray.ang.AppConfig
|
|
||||||
import com.v2ray.ang.R
|
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
|
||||||
import com.v2ray.ang.dto.AngConfig
|
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
|
||||||
import com.v2ray.ang.util.Utils
|
|
||||||
import kotlinx.android.synthetic.main.activity_server2.*
|
|
||||||
import org.jetbrains.anko.*
|
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
|
|
||||||
class Server2Activity : BaseActivity() {
|
|
||||||
companion object {
|
|
||||||
private const val REQUEST_SCAN = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var del_config: MenuItem? = null
|
|
||||||
var save_config: MenuItem? = null
|
|
||||||
|
|
||||||
private lateinit var configs: AngConfig
|
|
||||||
private var edit_index: Int = -1 //当前编辑的服务器
|
|
||||||
private var edit_guid: String = ""
|
|
||||||
private var isRunning: Boolean = false
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_server2)
|
|
||||||
|
|
||||||
configs = AngConfigManager.configs
|
|
||||||
edit_index = intent.getIntExtra("position", -1)
|
|
||||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
|
||||||
title = getString(R.string.title_server)
|
|
||||||
|
|
||||||
if (edit_index >= 0) {
|
|
||||||
edit_guid = configs.vmess[edit_index].guid
|
|
||||||
bindingServer(configs.vmess[edit_index])
|
|
||||||
} else {
|
|
||||||
clearServer()
|
|
||||||
}
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* bingding seleced server config
|
|
||||||
*/
|
|
||||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
|
||||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
|
||||||
tv_content.text = Editable.Factory.getInstance().newEditable(defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + edit_guid, ""))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* clear or init server config
|
|
||||||
*/
|
|
||||||
fun clearServer(): Boolean {
|
|
||||||
et_remarks.text = null
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* save server config
|
|
||||||
*/
|
|
||||||
fun saveServer(): Boolean {
|
|
||||||
var saveSuccess: Boolean
|
|
||||||
val vmess = configs.vmess[edit_index]
|
|
||||||
|
|
||||||
vmess.remarks = et_remarks.text.toString()
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(vmess.remarks)) {
|
|
||||||
toast(R.string.server_lab_remarks)
|
|
||||||
saveSuccess = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
|
|
||||||
toast(R.string.toast_success)
|
|
||||||
saveSuccess = true
|
|
||||||
} else {
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
saveSuccess = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
Gson().fromJson<Object>(tv_content.text.toString(), Object::class.java)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
toast(R.string.toast_malformed_josn)
|
|
||||||
saveSuccess = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (saveSuccess) {
|
|
||||||
//update config
|
|
||||||
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
|
|
||||||
finish()
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
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,175 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
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,54 +1,116 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.AngConfig
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
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.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.android.synthetic.main.activity_server.*
|
import kotlinx.android.synthetic.main.activity_server_socks.*
|
||||||
import org.jetbrains.anko.*
|
import kotlinx.android.synthetic.main.activity_server_vmess.*
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.et_address
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.et_id
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.et_path
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.et_port
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.et_remarks
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.et_request_host
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.sp_allow_insecure
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type_title
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.sp_network
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_vmess.sp_stream_security
|
||||||
|
|
||||||
class ServerActivity : BaseActivity() {
|
class ServerActivity : BaseActivity() {
|
||||||
companion object {
|
|
||||||
private const val REQUEST_SCAN = 1
|
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
|
||||||
|
private val isRunning by lazy {
|
||||||
|
intent.getBooleanExtra("isRunning", false)
|
||||||
|
&& editGuid.isNotEmpty()
|
||||||
|
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
|
||||||
|
}
|
||||||
|
private val createConfigType by lazy {
|
||||||
|
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
|
||||||
}
|
}
|
||||||
|
|
||||||
var del_config: MenuItem? = null
|
|
||||||
var save_config: MenuItem? = null
|
|
||||||
|
|
||||||
private lateinit var configs: AngConfig
|
|
||||||
private var edit_index: Int = -1 //当前编辑的服务器
|
|
||||||
private var edit_guid: String = ""
|
|
||||||
private var isRunning: Boolean = false
|
|
||||||
private val securitys: Array<out String> by lazy {
|
private val securitys: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.securitys)
|
resources.getStringArray(R.array.securitys)
|
||||||
}
|
}
|
||||||
|
private val shadowsocksSecuritys: Array<out String> by lazy {
|
||||||
|
resources.getStringArray(R.array.ss_securitys)
|
||||||
|
}
|
||||||
|
private val flows: Array<out String> by lazy {
|
||||||
|
resources.getStringArray(R.array.flows)
|
||||||
|
}
|
||||||
private val networks: Array<out String> by lazy {
|
private val networks: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.networks)
|
resources.getStringArray(R.array.networks)
|
||||||
}
|
}
|
||||||
private val headertypes: Array<out String> by lazy {
|
private val tcpTypes: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.headertypes)
|
resources.getStringArray(R.array.header_type_tcp)
|
||||||
}
|
}
|
||||||
private val streamsecuritys: Array<out String> by lazy {
|
private val kcpAndQuicTypes: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.streamsecuritys)
|
resources.getStringArray(R.array.header_type_kcp_and_quic)
|
||||||
|
}
|
||||||
|
private val grpcModes: Array<out String> by lazy {
|
||||||
|
resources.getStringArray(R.array.mode_type_grpc)
|
||||||
|
}
|
||||||
|
private val streamSecuritys: Array<out String> by lazy {
|
||||||
|
resources.getStringArray(R.array.streamsecurityxs)
|
||||||
|
}
|
||||||
|
private val allowinsecures: Array<out String> by lazy {
|
||||||
|
resources.getStringArray(R.array.allowinsecures)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_server)
|
|
||||||
|
|
||||||
configs = AngConfigManager.configs
|
|
||||||
edit_index = intent.getIntExtra("position", -1)
|
|
||||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
|
||||||
title = getString(R.string.title_server)
|
title = getString(R.string.title_server)
|
||||||
|
|
||||||
if (edit_index >= 0) {
|
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||||
edit_guid = configs.vmess[edit_index].guid
|
when(config?.configType ?: createConfigType) {
|
||||||
bindingServer(configs.vmess[edit_index])
|
EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
|
||||||
|
EConfigType.CUSTOM -> return
|
||||||
|
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
||||||
|
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||||
|
// EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||||
|
// EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||||
|
}
|
||||||
|
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
val types = transportTypes(networks[position])
|
||||||
|
sp_header_type?.isEnabled = types.size > 1
|
||||||
|
val adapter = ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
sp_header_type?.adapter = adapter
|
||||||
|
sp_header_type_title?.text = if (networks[position] == "grpc")
|
||||||
|
getString(R.string.server_lab_mode_type) else
|
||||||
|
getString(R.string.server_lab_head_type)
|
||||||
|
config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
|
sp_header_type.setSelection(Utils.arrayFind(types, transportDetails[0]))
|
||||||
|
et_request_host.text = Utils.getEditable(transportDetails[1])
|
||||||
|
et_path.text = Utils.getEditable(transportDetails[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config != null) {
|
||||||
|
bindingServer(config)
|
||||||
} else {
|
} else {
|
||||||
clearServer()
|
clearServer()
|
||||||
}
|
}
|
||||||
@@ -58,33 +120,44 @@ class ServerActivity : BaseActivity() {
|
|||||||
/**
|
/**
|
||||||
* bingding seleced server config
|
* bingding seleced server config
|
||||||
*/
|
*/
|
||||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
private fun bindingServer(config: ServerConfig): Boolean {
|
||||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
val outbound = config.getProxyOutbound() ?: return false
|
||||||
|
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||||
|
|
||||||
et_address.text = Utils.getEditable(vmess.address)
|
et_remarks.text = Utils.getEditable(config.remarks)
|
||||||
et_port.text = Utils.getEditable(vmess.port.toString())
|
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
|
||||||
et_id.text = Utils.getEditable(vmess.id)
|
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
|
||||||
et_alterId.text = Utils.getEditable(vmess.alterId.toString())
|
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
|
||||||
|
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
|
||||||
val security = Utils.arrayFind(securitys, vmess.security)
|
if (config.configType == EConfigType.SOCKS) {
|
||||||
|
et_security.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
||||||
|
} else if (config.configType == EConfigType.VLESS) {
|
||||||
|
et_security.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
|
||||||
|
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
|
||||||
|
if (flow >= 0) {
|
||||||
|
//sp_flow.setSelection(flow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||||
|
val security = Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
|
||||||
if (security >= 0) {
|
if (security >= 0) {
|
||||||
sp_security.setSelection(security)
|
sp_security?.setSelection(security)
|
||||||
}
|
|
||||||
val network = Utils.arrayFind(networks, vmess.network)
|
|
||||||
if (network >= 0) {
|
|
||||||
sp_network.setSelection(network)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val headerType = Utils.arrayFind(headertypes, vmess.headerType)
|
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
|
||||||
if (headerType >= 0) {
|
|
||||||
sp_header_type.setSelection(headerType)
|
|
||||||
}
|
|
||||||
et_request_host.text = Utils.getEditable(vmess.requestHost)
|
|
||||||
et_path.text = Utils.getEditable(vmess.path)
|
|
||||||
|
|
||||||
val streamSecurity = Utils.arrayFind(streamsecuritys, vmess.streamSecurity)
|
|
||||||
if (streamSecurity >= 0) {
|
if (streamSecurity >= 0) {
|
||||||
sp_stream_security.setSelection(streamSecurity)
|
sp_stream_security?.setSelection(streamSecurity)
|
||||||
|
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
||||||
|
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
|
||||||
|
if (allowinsecure >= 0) {
|
||||||
|
sp_allow_insecure?.setSelection(allowinsecure)
|
||||||
|
}
|
||||||
|
et_request_host.text = Utils.getEditable(tlsSetting.serverName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val network = Utils.arrayFind(networks, streamSetting.network)
|
||||||
|
if (network >= 0) {
|
||||||
|
sp_network?.setSelection(network)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -92,113 +165,179 @@ class ServerActivity : BaseActivity() {
|
|||||||
/**
|
/**
|
||||||
* clear or init server config
|
* clear or init server config
|
||||||
*/
|
*/
|
||||||
fun clearServer(): Boolean {
|
private fun clearServer(): Boolean {
|
||||||
et_remarks.text = null
|
et_remarks.text = null
|
||||||
et_address.text = null
|
et_address.text = null
|
||||||
et_port.text = Utils.getEditable("10086")
|
et_port.text = Utils.getEditable(DEFAULT_PORT.toString())
|
||||||
et_id.text = null
|
et_id.text = null
|
||||||
et_alterId.text = Utils.getEditable("64")
|
et_alterId?.text = Utils.getEditable("0")
|
||||||
sp_security.setSelection(0)
|
sp_security?.setSelection(0)
|
||||||
sp_network.setSelection(0)
|
sp_network?.setSelection(0)
|
||||||
|
|
||||||
sp_header_type.setSelection(0)
|
sp_header_type?.setSelection(0)
|
||||||
et_request_host.text = null
|
et_request_host?.text = null
|
||||||
et_path.text = null
|
et_path?.text = null
|
||||||
sp_stream_security.setSelection(0)
|
sp_stream_security?.setSelection(0)
|
||||||
|
sp_allow_insecure?.setSelection(0)
|
||||||
|
|
||||||
|
//et_security.text = null
|
||||||
|
//sp_flow?.setSelection(0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save server config
|
* save server config
|
||||||
*/
|
*/
|
||||||
fun saveServer(): Boolean {
|
private fun saveServer(): Boolean {
|
||||||
val vmess: AngConfig.VmessBean
|
if (TextUtils.isEmpty(et_remarks.text.toString())) {
|
||||||
if (edit_index >= 0) {
|
|
||||||
vmess = configs.vmess[edit_index]
|
|
||||||
} else {
|
|
||||||
vmess = AngConfig.VmessBean()
|
|
||||||
}
|
|
||||||
|
|
||||||
vmess.guid = edit_guid
|
|
||||||
vmess.remarks = et_remarks.text.toString()
|
|
||||||
vmess.address = et_address.text.toString()
|
|
||||||
vmess.port = Utils.parseInt(et_port.text.toString())
|
|
||||||
vmess.id = et_id.text.toString()
|
|
||||||
vmess.alterId = Utils.parseInt(et_alterId.text.toString())
|
|
||||||
vmess.security = securitys[sp_security.selectedItemPosition]
|
|
||||||
vmess.network = networks[sp_network.selectedItemPosition]
|
|
||||||
|
|
||||||
vmess.headerType = headertypes[sp_header_type.selectedItemPosition]
|
|
||||||
vmess.requestHost = et_request_host.text.toString()
|
|
||||||
vmess.path = et_path.text.toString()
|
|
||||||
vmess.streamSecurity = streamsecuritys[sp_stream_security.selectedItemPosition]
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(vmess.remarks)) {
|
|
||||||
toast(R.string.server_lab_remarks)
|
toast(R.string.server_lab_remarks)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(vmess.address)) {
|
if (TextUtils.isEmpty(et_address.text.toString())) {
|
||||||
toast(R.string.server_lab_address)
|
toast(R.string.server_lab_address)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
|
val port = Utils.parseInt(et_port.text.toString())
|
||||||
|
if (port <= 0) {
|
||||||
toast(R.string.server_lab_port)
|
toast(R.string.server_lab_port)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(vmess.id)) {
|
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
|
||||||
|
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
|
||||||
toast(R.string.server_lab_id)
|
toast(R.string.server_lab_id)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(vmess.alterId.toString()) || vmess.alterId < 0) {
|
et_alterId?.let {
|
||||||
toast(R.string.server_lab_alterid)
|
val alterId = Utils.parseInt(et_alterId.text.toString())
|
||||||
return false
|
if (alterId < 0) {
|
||||||
|
toast(R.string.server_lab_alterid)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AngConfigManager.addServer(vmess, edit_index) == 0) {
|
config.remarks = et_remarks.text.toString().trim()
|
||||||
toast(R.string.toast_success)
|
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
finish()
|
saveVnext(vnext, port, config)
|
||||||
return true
|
}
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
saveServers(server, port, config)
|
||||||
|
}
|
||||||
|
config.outboundBean?.streamSettings?.let {
|
||||||
|
saveStreamSettings(it, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
finish()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveVnext(vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean, port: Int, config: ServerConfig) {
|
||||||
|
vnext.address = et_address.text.toString().trim()
|
||||||
|
vnext.port = port
|
||||||
|
vnext.users[0].id = et_id.text.toString().trim()
|
||||||
|
if (config.configType == EConfigType.VMESS) {
|
||||||
|
vnext.users[0].alterId = Utils.parseInt(et_alterId.text.toString())
|
||||||
|
vnext.users[0].security = securitys[sp_security.selectedItemPosition]
|
||||||
|
} else if (config.configType == EConfigType.VLESS) {
|
||||||
|
vnext.users[0].encryption = et_security.text.toString().trim()
|
||||||
|
if (streamSecuritys[sp_stream_security.selectedItemPosition] == XTLS) {
|
||||||
|
// vnext.users[0].flow = if (flows[sp_flow.selectedItemPosition].isBlank()) V2rayConfig.DEFAULT_FLOW
|
||||||
|
// else flows[sp_flow.selectedItemPosition]
|
||||||
|
} else {
|
||||||
|
vnext.users[0].flow = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveServers(server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean, port: Int, config: ServerConfig) {
|
||||||
|
server.address = et_address.text.toString().trim()
|
||||||
|
server.port = port
|
||||||
|
if (config.configType == EConfigType.SHADOWSOCKS) {
|
||||||
|
server.password = et_id.text.toString().trim()
|
||||||
|
server.method = shadowsocksSecuritys[sp_security.selectedItemPosition]
|
||||||
|
} else if (config.configType == EConfigType.SOCKS) {
|
||||||
|
if (TextUtils.isEmpty(et_security.text) && TextUtils.isEmpty(et_id.text)) {
|
||||||
|
server.users = null
|
||||||
|
} else {
|
||||||
|
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||||
|
socksUsersBean.user = et_security.text.toString().trim()
|
||||||
|
socksUsersBean.pass = et_id.text.toString().trim()
|
||||||
|
server.users = listOf(socksUsersBean)
|
||||||
|
}
|
||||||
|
} else if (config.configType == EConfigType.TROJAN) {
|
||||||
|
server.password = et_id.text.toString().trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, config: ServerConfig) {
|
||||||
|
val network = if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK
|
||||||
|
val type = if (sp_header_type != null) transportTypes(network)[sp_header_type.selectedItemPosition] else "";
|
||||||
|
val requestHost = if (et_request_host != null) et_request_host.text.toString().trim() else ""
|
||||||
|
val path = if (et_path != null) et_path.text.toString().trim() else ""
|
||||||
|
var sni = streamSetting.populateTransportSettings(
|
||||||
|
transport = network,
|
||||||
|
headerType = type,
|
||||||
|
host = requestHost,
|
||||||
|
path = path,
|
||||||
|
seed = path,
|
||||||
|
quicSecurity = requestHost,
|
||||||
|
key = path,
|
||||||
|
mode = type,
|
||||||
|
serviceName = path
|
||||||
|
)
|
||||||
|
val allowInsecure = if (sp_allow_insecure == null || allowinsecures[sp_allow_insecure.selectedItemPosition].isBlank()) {
|
||||||
|
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.toast_failure)
|
allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean()
|
||||||
return false
|
}
|
||||||
|
val defaultTls = if (config.configType == EConfigType.TROJAN) V2rayConfig.TLS else ""
|
||||||
|
streamSetting.populateTlsSettings(
|
||||||
|
if (sp_stream_security != null) streamSecuritys[sp_stream_security.selectedItemPosition] else defaultTls,
|
||||||
|
allowInsecure,
|
||||||
|
sni
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transportTypes(network: String?): Array<out String> {
|
||||||
|
return if (network == "tcp") {
|
||||||
|
tcpTypes
|
||||||
|
} else if (network == "kcp" || network == "quic") {
|
||||||
|
kcpAndQuicTypes
|
||||||
|
} else if (network == "grpc") {
|
||||||
|
grpcModes
|
||||||
|
} else {
|
||||||
|
arrayOf("---")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save server config
|
* save server config
|
||||||
*/
|
*/
|
||||||
fun deleteServer(): Boolean {
|
private fun deleteServer(): Boolean {
|
||||||
if (edit_index >= 0) {
|
if (editGuid.isNotEmpty()) {
|
||||||
alert(R.string.del_config_comfirm) {
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
positiveButton(android.R.string.ok) {
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
MmkvManager.removeServer(editGuid)
|
||||||
toast(R.string.toast_success)
|
|
||||||
finish()
|
finish()
|
||||||
} else {
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
}
|
}
|
||||||
}
|
.show()
|
||||||
show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_server, menu)
|
menuInflater.inflate(R.menu.action_server, menu)
|
||||||
del_config = menu?.findItem(R.id.del_config)
|
val delButton = menu?.findItem(R.id.del_config)
|
||||||
save_config = menu?.findItem(R.id.save_config)
|
val saveButton = menu?.findItem(R.id.save_config)
|
||||||
|
|
||||||
if (edit_index >= 0) {
|
if (editGuid.isNotEmpty()) {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
if (edit_index == configs.index) {
|
delButton?.isVisible = false
|
||||||
del_config?.isVisible = false
|
saveButton?.isVisible = false
|
||||||
save_config?.isVisible = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
del_config?.isVisible = false
|
delButton?.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
@@ -215,4 +354,4 @@ class ServerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import kotlinx.android.synthetic.main.activity_server_custom_config.*
|
||||||
|
import me.drakeet.support.toast.ToastCompat
|
||||||
|
|
||||||
|
class ServerCustomConfigActivity : BaseActivity() {
|
||||||
|
|
||||||
|
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
|
||||||
|
private val isRunning by lazy {
|
||||||
|
intent.getBooleanExtra("isRunning", false)
|
||||||
|
&& editGuid.isNotEmpty()
|
||||||
|
&& editGuid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_server_custom_config)
|
||||||
|
title = getString(R.string.title_server)
|
||||||
|
|
||||||
|
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||||
|
if (config != null) {
|
||||||
|
bindingServer(config)
|
||||||
|
} else {
|
||||||
|
clearServer()
|
||||||
|
}
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bingding seleced server config
|
||||||
|
*/
|
||||||
|
private fun bindingServer(config: ServerConfig): Boolean {
|
||||||
|
et_remarks.text = Utils.getEditable(config.remarks)
|
||||||
|
val raw = serverRawStorage?.decodeString(editGuid)
|
||||||
|
if (raw.isNullOrBlank()) {
|
||||||
|
tv_content.text = Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty())
|
||||||
|
} else {
|
||||||
|
tv_content.text = Utils.getEditable(raw)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear or init server config
|
||||||
|
*/
|
||||||
|
private fun clearServer(): Boolean {
|
||||||
|
et_remarks.text = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save server config
|
||||||
|
*/
|
||||||
|
private fun saveServer(): Boolean {
|
||||||
|
if (TextUtils.isEmpty(et_remarks.text.toString())) {
|
||||||
|
toast(R.string.server_lab_remarks)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val v2rayConfig = try {
|
||||||
|
Gson().fromJson(tv_content.text.toString(), V2rayConfig::class.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
|
||||||
|
config.remarks = et_remarks.text.toString().trim()
|
||||||
|
config.fullConfig = v2rayConfig
|
||||||
|
|
||||||
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
|
serverRawStorage?.encode(editGuid, tv_content.text.toString())
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
finish()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save server config
|
||||||
|
*/
|
||||||
|
private fun deleteServer(): Boolean {
|
||||||
|
if (editGuid.isNotEmpty()) {
|
||||||
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
MmkvManager.removeServer(editGuid)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.action_server, menu)
|
||||||
|
val delButton = menu?.findItem(R.id.del_config)
|
||||||
|
val saveButton = menu?.findItem(R.id.save_config)
|
||||||
|
|
||||||
|
if (editGuid.isNotEmpty()) {
|
||||||
|
if (isRunning) {
|
||||||
|
delButton?.isVisible = false
|
||||||
|
saveButton?.isVisible = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delButton?.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
|
R.id.del_config -> {
|
||||||
|
deleteServer()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.save_config -> {
|
||||||
|
saveServer()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,51 +1,21 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.arch.lifecycle.ViewModelProviders
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.preference.*
|
import android.support.v7.preference.*
|
||||||
import com.v2ray.ang.AngApplication
|
import android.text.TextUtils
|
||||||
import com.v2ray.ang.BuildConfig
|
import android.view.View
|
||||||
//import com.v2ray.ang.InappBuyActivity
|
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.onClick
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import org.jetbrains.anko.act
|
import com.v2ray.ang.viewmodel.SettingsViewModel
|
||||||
import org.jetbrains.anko.defaultSharedPreferences
|
|
||||||
import org.jetbrains.anko.startActivity
|
|
||||||
import org.jetbrains.anko.toast
|
|
||||||
import libv2ray.Libv2ray
|
|
||||||
|
|
||||||
class SettingsActivity : BaseActivity() {
|
class SettingsActivity : BaseActivity() {
|
||||||
companion object {
|
|
||||||
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
|
||||||
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
|
|
||||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
|
||||||
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
|
|
||||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
|
||||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
|
||||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
|
||||||
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
|
||||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
|
||||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
|
||||||
|
|
||||||
// const val PREF_SOCKS_PORT = "pref_socks_port"
|
private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) }
|
||||||
// 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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -54,20 +24,26 @@ class SettingsActivity : BaseActivity() {
|
|||||||
title = getString(R.string.title_settings)
|
title = getString(R.string.title_settings)
|
||||||
|
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
settingsViewModel.startListenPreferenceChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
|
private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||||
val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference }
|
private val localDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
||||||
val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
private val fakeDns by lazy { findPreference(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
||||||
val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference }
|
private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||||
val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) }
|
||||||
val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference }
|
private val sppedEnabled by lazy { findPreference(AppConfig.PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||||
|
private val sniffingEnabled by lazy { findPreference(AppConfig.PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||||
|
private val proxySharing by lazy { findPreference(AppConfig.PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||||
|
private val domainStrategy by lazy { findPreference(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||||
|
private val routingMode by lazy { findPreference(AppConfig.PREF_ROUTING_MODE) as ListPreference }
|
||||||
|
|
||||||
val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
|
private val forwardIpv6 by lazy { findPreference(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||||
val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||||
val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
|
private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||||
val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
|
private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
|
||||||
|
|
||||||
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
||||||
|
|
||||||
@@ -75,32 +51,31 @@ class SettingsActivity : BaseActivity() {
|
|||||||
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
|
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
|
||||||
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
|
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
|
||||||
|
|
||||||
val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) }
|
private val routingCustom: Preference by lazy { findPreference(AppConfig.PREF_ROUTING_CUSTOM) }
|
||||||
// val donate: Preference by lazy { findPreference(PREF_DONATE) }
|
// val donate: Preference by lazy { findPreference(PREF_DONATE) }
|
||||||
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
||||||
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
||||||
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
|
// 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() {
|
private fun restartProxy() {
|
||||||
Utils.stopVService(activity)
|
Utils.stopVService(requireContext())
|
||||||
Utils.startVService(activity)
|
V2RayServiceManager.startV2Ray(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isRunning(): Boolean {
|
private fun isRunning(): Boolean {
|
||||||
return Utils.isServiceRun(activity, "com.v2ray.ang.service.V2RayVpnService")
|
return false //TODO no point of adding logic now since Settings will be changed soon
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
addPreferencesFromResource(R.xml.pref_settings)
|
addPreferencesFromResource(R.xml.pref_settings)
|
||||||
var app = activity.application as AngApplication
|
|
||||||
|
|
||||||
perAppProxy.setOnPreferenceClickListener {
|
perAppProxy.setOnPreferenceClickListener {
|
||||||
if (isRunning()) {
|
if (isRunning()) {
|
||||||
Utils.stopVService(activity)
|
Utils.stopVService(requireContext())
|
||||||
}
|
}
|
||||||
startActivity<PerAppProxyActivity>()
|
startActivity(Intent(activity, PerAppProxyActivity::class.java))
|
||||||
perAppProxy.isChecked = true
|
perAppProxy.isChecked = true
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -117,7 +92,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
|
|
||||||
proxySharing.setOnPreferenceClickListener {
|
proxySharing.setOnPreferenceClickListener {
|
||||||
if (proxySharing.isChecked)
|
if (proxySharing.isChecked)
|
||||||
toast(R.string.toast_warning_pref_proxysharing)
|
activity?.toast(R.string.toast_warning_pref_proxysharing)
|
||||||
if (isRunning())
|
if (isRunning())
|
||||||
restartProxy()
|
restartProxy()
|
||||||
true
|
true
|
||||||
@@ -134,10 +109,11 @@ class SettingsActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
routingCustom.onClick {
|
routingCustom.setOnPreferenceClickListener {
|
||||||
if (isRunning())
|
if (isRunning())
|
||||||
Utils.stopVService(activity)
|
Utils.stopVService(requireContext())
|
||||||
startActivity<RoutingSettingsActivity>()
|
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
|
||||||
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardIpv6.setOnPreferenceClickListener {
|
forwardIpv6.setOnPreferenceClickListener {
|
||||||
@@ -153,7 +129,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
domesticDns.setOnPreferenceChangeListener { preference, any ->
|
domesticDns.setOnPreferenceChangeListener { _, any ->
|
||||||
// domesticDns.summary = any as String
|
// domesticDns.summary = any as String
|
||||||
val nval = any as String
|
val nval = any as String
|
||||||
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
||||||
@@ -162,7 +138,7 @@ class SettingsActivity : BaseActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteDns.setOnPreferenceChangeListener { preference, any ->
|
remoteDns.setOnPreferenceChangeListener { _, any ->
|
||||||
// remoteDns.summary = any as String
|
// remoteDns.summary = any as String
|
||||||
val nval = any as String
|
val nval = any as String
|
||||||
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
|
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
|
||||||
@@ -170,6 +146,24 @@ class SettingsActivity : BaseActivity() {
|
|||||||
restartProxy()
|
restartProxy()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
localDns?.setOnPreferenceChangeListener{ _, any ->
|
||||||
|
updateLocalDns(any as Boolean)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
localDnsPort?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) "10807" else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
vpnDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
vpnDns?.summary = any as String
|
||||||
|
true
|
||||||
|
}
|
||||||
|
mode.setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
updateMode(newValue.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
mode.dialogLayoutResource = R.layout.preference_with_help_link
|
||||||
|
|
||||||
// donate.onClick {
|
// donate.onClick {
|
||||||
// startActivity<InappBuyActivity>()
|
// startActivity<InappBuyActivity>()
|
||||||
@@ -206,45 +200,51 @@ class SettingsActivity : BaseActivity() {
|
|||||||
// httpPort.summary = any as String
|
// httpPort.summary = any as String
|
||||||
// true
|
// true
|
||||||
// }
|
// }
|
||||||
|
|
||||||
version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.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, "")
|
||||||
|
|
||||||
perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false)
|
if (TextUtils.isEmpty(remoteDnsString)) {
|
||||||
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "")
|
remoteDnsString = AppConfig.DNS_AGENT
|
||||||
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "")
|
|
||||||
|
|
||||||
if (remoteDns.summary == "") {
|
|
||||||
remoteDns.summary = AppConfig.DNS_AGENT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( domesticDns.summary == "") {
|
if ( domesticDns.summary == "") {
|
||||||
domesticDns.summary = AppConfig.DNS_DIRECT
|
domesticDns.summary = AppConfig.DNS_DIRECT
|
||||||
}
|
}
|
||||||
|
remoteDns.summary = remoteDnsString
|
||||||
|
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
|
||||||
|
|
||||||
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
|
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
|
||||||
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
|
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
|
||||||
|
|
||||||
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
private fun updateMode(mode: String?) {
|
||||||
super.onStop()
|
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
defaultSharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
val vpn = mode == "VPN"
|
||||||
}
|
perAppProxy.isEnabled = vpn
|
||||||
|
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||||
when (key) {
|
localDns?.isEnabled = vpn
|
||||||
// PREF_AUTO_RESTART ->
|
fakeDns?.isEnabled = vpn
|
||||||
// act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
|
localDnsPort?.isEnabled = vpn
|
||||||
|
vpnDns?.isEnabled = vpn
|
||||||
PREF_PER_APP_PROXY ->
|
if (vpn) {
|
||||||
act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
|
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateLocalDns(enabled: Boolean) {
|
||||||
|
fakeDns?.isEnabled = enabled
|
||||||
|
localDnsPort?.isEnabled = enabled
|
||||||
|
vpnDns?.isEnabled = !enabled
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
fun onModeHelpClicked(view: View) {
|
||||||
|
Utils.openUri(this, AppConfig.v2rayNGWikiMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.support.v7.app.AlertDialog
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.AngConfig
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.android.synthetic.main.activity_sub_edit.*
|
import kotlinx.android.synthetic.main.activity_sub_edit.*
|
||||||
import org.jetbrains.anko.*
|
|
||||||
|
|
||||||
|
|
||||||
class SubEditActivity : BaseActivity() {
|
class SubEditActivity : BaseActivity() {
|
||||||
|
|
||||||
var del_config: MenuItem? = null
|
var del_config: MenuItem? = null
|
||||||
var save_config: MenuItem? = null
|
var save_config: MenuItem? = null
|
||||||
|
|
||||||
private lateinit var configs: AngConfig
|
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private var edit_index: Int = -1 //当前编辑的
|
private val editSubId by lazy { intent.getStringExtra("subId").orEmpty() }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_sub_edit)
|
setContentView(R.layout.activity_sub_edit)
|
||||||
|
|
||||||
configs = AngConfigManager.configs
|
|
||||||
edit_index = intent.getIntExtra("position", -1)
|
|
||||||
|
|
||||||
title = getString(R.string.title_sub_setting)
|
title = getString(R.string.title_sub_setting)
|
||||||
|
|
||||||
if (edit_index >= 0) {
|
val json = subStorage?.decodeString(editSubId)
|
||||||
bindingServer(configs.subItem[edit_index])
|
if (!json.isNullOrBlank()) {
|
||||||
|
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
|
||||||
} else {
|
} else {
|
||||||
clearServer()
|
clearServer()
|
||||||
}
|
}
|
||||||
@@ -40,7 +39,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
/**
|
/**
|
||||||
* bingding seleced server config
|
* bingding seleced server config
|
||||||
*/
|
*/
|
||||||
fun bindingServer(subItem: AngConfig.SubItemBean): Boolean {
|
private fun bindingServer(subItem: SubscriptionItem): Boolean {
|
||||||
et_remarks.text = Utils.getEditable(subItem.remarks)
|
et_remarks.text = Utils.getEditable(subItem.remarks)
|
||||||
et_url.text = Utils.getEditable(subItem.url)
|
et_url.text = Utils.getEditable(subItem.url)
|
||||||
|
|
||||||
@@ -50,7 +49,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
/**
|
/**
|
||||||
* clear or init server config
|
* clear or init server config
|
||||||
*/
|
*/
|
||||||
fun clearServer(): Boolean {
|
private fun clearServer(): Boolean {
|
||||||
et_remarks.text = null
|
et_remarks.text = null
|
||||||
et_url.text = null
|
et_url.text = null
|
||||||
|
|
||||||
@@ -60,12 +59,15 @@ class SubEditActivity : BaseActivity() {
|
|||||||
/**
|
/**
|
||||||
* save server config
|
* save server config
|
||||||
*/
|
*/
|
||||||
fun saveServer(): Boolean {
|
private fun saveServer(): Boolean {
|
||||||
val subItem: AngConfig.SubItemBean
|
val subItem: SubscriptionItem
|
||||||
if (edit_index >= 0) {
|
val json = subStorage?.decodeString(editSubId)
|
||||||
subItem = configs.subItem[edit_index]
|
var subId = editSubId
|
||||||
|
if (!json.isNullOrBlank()) {
|
||||||
|
subItem = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||||
} else {
|
} else {
|
||||||
subItem = AngConfig.SubItemBean()
|
subId = Utils.getUuid()
|
||||||
|
subItem = SubscriptionItem()
|
||||||
}
|
}
|
||||||
|
|
||||||
subItem.remarks = et_remarks.text.toString()
|
subItem.remarks = et_remarks.text.toString()
|
||||||
@@ -80,33 +82,23 @@ class SubEditActivity : BaseActivity() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AngConfigManager.addSubItem(subItem, edit_index) == 0) {
|
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
finish()
|
finish()
|
||||||
return true
|
return true
|
||||||
} else {
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save server config
|
* save server config
|
||||||
*/
|
*/
|
||||||
fun deleteServer(): Boolean {
|
private fun deleteServer(): Boolean {
|
||||||
if (edit_index >= 0) {
|
if (editSubId.isNotEmpty()) {
|
||||||
alert(R.string.del_config_comfirm) {
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
positiveButton(android.R.string.ok) {
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (AngConfigManager.removeSubItem(edit_index) == 0) {
|
MmkvManager.removeSubscription(editSubId)
|
||||||
toast(R.string.toast_success)
|
|
||||||
finish()
|
finish()
|
||||||
} else {
|
|
||||||
toast(R.string.toast_failure)
|
|
||||||
}
|
}
|
||||||
}
|
.show()
|
||||||
show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -116,8 +108,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
del_config = menu?.findItem(R.id.del_config)
|
del_config = menu?.findItem(R.id.del_config)
|
||||||
save_config = menu?.findItem(R.id.save_config)
|
save_config = menu?.findItem(R.id.save_config)
|
||||||
|
|
||||||
if (edit_index >= 0) {
|
if (editSubId.isEmpty()) {
|
||||||
} else {
|
|
||||||
del_config?.isVisible = false
|
del_config?.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,4 +126,4 @@ class SubEditActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.support.v7.widget.LinearLayoutManager
|
import android.content.Intent
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import kotlinx.android.synthetic.main.activity_sub_setting.*
|
import kotlinx.android.synthetic.main.activity_sub_setting.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import org.jetbrains.anko.startActivity
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
|
||||||
class SubSettingActivity : BaseActivity() {
|
class SubSettingActivity : BaseActivity() {
|
||||||
|
|
||||||
|
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
|
||||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -27,7 +30,8 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
adapter.updateConfigList()
|
subscriptions = MmkvManager.decodeSubscriptions()
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
@@ -40,12 +44,9 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.add_config -> {
|
R.id.add_config -> {
|
||||||
startActivity<SubEditActivity>("position" to -1)
|
startActivity(Intent(this, SubEditActivity::class.java))
|
||||||
adapter.updateConfigList()
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,56 +1,41 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.AngConfig
|
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
|
||||||
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
|
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
|
||||||
import org.jetbrains.anko.*
|
|
||||||
|
|
||||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
|
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
|
||||||
|
|
||||||
private var mActivity: SubSettingActivity = activity
|
private var mActivity: SubSettingActivity = activity
|
||||||
private lateinit var configs: AngConfig
|
|
||||||
|
|
||||||
init {
|
override fun getItemCount() = mActivity.subscriptions.size
|
||||||
updateConfigList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = configs.subItem.count()
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||||
if (holder is MainViewHolder) {
|
if (holder is MainViewHolder) {
|
||||||
val remarks = configs.subItem[position].remarks
|
val subId = mActivity.subscriptions[position].first
|
||||||
val url = configs.subItem[position].url
|
val subItem = mActivity.subscriptions[position].second
|
||||||
|
holder.name.text = subItem.remarks
|
||||||
holder.name.text = remarks
|
holder.url.text = subItem.url
|
||||||
holder.url.text = url
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
holder.itemView.backgroundColor = Color.TRANSPARENT
|
|
||||||
|
|
||||||
holder.layout_edit.setOnClickListener {
|
holder.layout_edit.setOnClickListener {
|
||||||
mActivity.startActivity<SubEditActivity>("position" to position)
|
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
|
||||||
|
.putExtra("subId", subId)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||||
return MainViewHolder(parent.context.layoutInflater
|
return MainViewHolder(LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.item_recycler_sub_setting, parent, false))
|
.inflate(R.layout.item_recycler_sub_setting, parent, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateConfigList() {
|
|
||||||
configs = AngConfigManager.configs
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun updateSelectedItem() {
|
|
||||||
// notifyItemChanged(configs.index)
|
|
||||||
// }
|
|
||||||
|
|
||||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||||
|
|
||||||
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {
|
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {
|
||||||
|
|||||||
@@ -7,21 +7,23 @@ import android.widget.ArrayAdapter
|
|||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.util.AngConfigManager
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.google.zxing.WriterException
|
import com.google.zxing.WriterException
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
import kotlinx.android.synthetic.main.activity_tasker.*
|
import kotlinx.android.synthetic.main.activity_tasker.*
|
||||||
|
|
||||||
|
|
||||||
class TaskerActivity : BaseActivity() {
|
class TaskerActivity : BaseActivity() {
|
||||||
private var listview: ListView? = null
|
private var listview: ListView? = null
|
||||||
private var lstData: ArrayList<String> = ArrayList()
|
private var lstData: ArrayList<String> = ArrayList()
|
||||||
private var lstGuid: ArrayList<String> = ArrayList()
|
private var lstGuid: ArrayList<String> = ArrayList()
|
||||||
|
|
||||||
|
private val serverStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_tasker)
|
setContentView(R.layout.activity_tasker)
|
||||||
@@ -30,9 +32,11 @@ class TaskerActivity : BaseActivity() {
|
|||||||
lstData.add("Default")
|
lstData.add("Default")
|
||||||
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
|
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
|
||||||
|
|
||||||
AngConfigManager.configs.vmess.forEach {
|
serverStorage?.allKeys()?.forEach { key ->
|
||||||
lstData.add(it.remarks)
|
MmkvManager.decodeServerConfig(key)?.let { config ->
|
||||||
lstGuid.add(it.guid)
|
lstData.add(config.remarks)
|
||||||
|
lstGuid.add(key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val adapter = ArrayAdapter(this,
|
val adapter = ArrayAdapter(this,
|
||||||
android.R.layout.simple_list_item_single_choice, lstData)
|
android.R.layout.simple_list_item_single_choice, lstData)
|
||||||
@@ -75,12 +79,10 @@ class TaskerActivity : BaseActivity() {
|
|||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
|
|
||||||
val remarks = lstData[position]
|
val remarks = lstData[position]
|
||||||
var blurb = ""
|
val blurb = if (switch_start_service.isChecked) {
|
||||||
|
"Start $remarks"
|
||||||
if (switch_start_service.isChecked) {
|
|
||||||
blurb = "Start $remarks"
|
|
||||||
} else {
|
} else {
|
||||||
blurb = "Stop $remarks"
|
"Stop $remarks"
|
||||||
}
|
}
|
||||||
|
|
||||||
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
||||||
@@ -108,4 +110,3 @@ class TaskerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ object AppManagerUtil {
|
|||||||
val apps = ArrayList<AppInfo>()
|
val apps = ArrayList<AppInfo>()
|
||||||
|
|
||||||
for (pkg in packages) {
|
for (pkg in packages) {
|
||||||
if (!pkg.hasInternetPermission) continue
|
if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
|
||||||
|
|
||||||
val applicationInfo = pkg.applicationInfo
|
val applicationInfo = pkg.applicationInfo
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ object AppManagerUtil {
|
|||||||
return apps
|
return apps
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create {
|
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
|
||||||
it.onNext(loadNetworkAppList(ctx))
|
it.onNext(loadNetworkAppList(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,4 +40,4 @@ object AppManagerUtil {
|
|||||||
val permissions = requestedPermissions
|
val permissions = requestedPermissions
|
||||||
return permissions?.any { it == Manifest.permission.INTERNET } ?: false
|
return permissions?.any { it == Manifest.permission.INTERNET } ?: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
148
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt
Normal file
148
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package com.v2ray.ang.util
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.dto.ServerAffiliationInfo
|
||||||
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
|
|
||||||
|
object MmkvManager {
|
||||||
|
const val ID_MAIN = "MAIN"
|
||||||
|
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
|
||||||
|
const val ID_SERVER_RAW = "SERVER_RAW"
|
||||||
|
const val ID_SERVER_AFF = "SERVER_AFF"
|
||||||
|
const val ID_SUB = "SUB"
|
||||||
|
const val ID_SETTING = "SETTING"
|
||||||
|
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
|
||||||
|
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
|
||||||
|
|
||||||
|
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
|
fun decodeServerList(): MutableList<String> {
|
||||||
|
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
|
||||||
|
return if (json.isNullOrBlank()) {
|
||||||
|
mutableListOf()
|
||||||
|
} else {
|
||||||
|
Gson().fromJson(json, Array<String>::class.java).toMutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeServerConfig(guid: String): ServerConfig? {
|
||||||
|
if (guid.isBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val json = serverStorage?.decodeString(guid)
|
||||||
|
if (json.isNullOrBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Gson().fromJson(json, ServerConfig::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||||
|
val key = if (guid.isBlank()) {
|
||||||
|
Utils.getUuid()
|
||||||
|
} else {
|
||||||
|
guid
|
||||||
|
}
|
||||||
|
serverStorage?.encode(key, Gson().toJson(config))
|
||||||
|
val serverList= decodeServerList()
|
||||||
|
if (!serverList.contains(key)) {
|
||||||
|
serverList.add(key)
|
||||||
|
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||||
|
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
|
||||||
|
mainStorage?.encode(KEY_SELECTED_SERVER, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeServer(guid: String) {
|
||||||
|
if (guid.isBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (mainStorage?.decodeString(KEY_SELECTED_SERVER) == guid) {
|
||||||
|
mainStorage?.remove(KEY_SELECTED_SERVER)
|
||||||
|
}
|
||||||
|
val serverList= decodeServerList()
|
||||||
|
serverList.remove(guid)
|
||||||
|
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||||
|
serverStorage?.remove(guid)
|
||||||
|
serverAffStorage?.remove(guid)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeServerViaSubid(subid: String) {
|
||||||
|
if (subid.isBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
serverStorage?.allKeys()?.forEach { key ->
|
||||||
|
decodeServerConfig(key)?.let { config ->
|
||||||
|
if (config.subscriptionId == subid) {
|
||||||
|
removeServer(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeServerAffiliationInfo(guid: String): ServerAffiliationInfo? {
|
||||||
|
if (guid.isBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val json = serverAffStorage?.decodeString(guid)
|
||||||
|
if (json.isNullOrBlank()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return Gson().fromJson(json, ServerAffiliationInfo::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
|
||||||
|
if (guid.isBlank()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
|
||||||
|
aff.testDelayMillis = testResult
|
||||||
|
serverAffStorage?.encode(guid, Gson().toJson(aff))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearAllTestDelayResults() {
|
||||||
|
serverAffStorage?.allKeys()?.forEach { key ->
|
||||||
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
|
aff.testDelayMillis = 0
|
||||||
|
serverAffStorage?.encode(key, Gson().toJson(aff))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun importUrlAsSubscription(url: String): Int {
|
||||||
|
val subscriptions = decodeSubscriptions()
|
||||||
|
subscriptions.forEach {
|
||||||
|
if (it.second.url == url) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val subItem = SubscriptionItem()
|
||||||
|
subItem.remarks = "import sub"
|
||||||
|
subItem.url = url
|
||||||
|
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
|
||||||
|
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
|
||||||
|
subStorage?.allKeys()?.forEach { key ->
|
||||||
|
val json = subStorage?.decodeString(key)
|
||||||
|
if (!json.isNullOrBlank()) {
|
||||||
|
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subscriptions.sortedBy { (_, value) -> value.addedTime }
|
||||||
|
return subscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeSubscription(subid: String) {
|
||||||
|
subStorage?.remove(subid)
|
||||||
|
removeServerViaSubid(subid)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,43 +11,30 @@ import com.google.zxing.qrcode.QRCodeWriter
|
|||||||
import com.google.zxing.EncodeHintType
|
import com.google.zxing.EncodeHintType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import android.app.ActivityManager
|
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.AssetManager
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.text.TextUtils
|
|
||||||
import android.text.method.ScrollingMovementMethod
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.view.View
|
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import com.v2ray.ang.AngApplication
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.responseLength
|
import com.v2ray.ang.extension.responseLength
|
||||||
import com.v2ray.ang.extension.v2RayApplication
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.service.V2RayVpnService
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
import com.v2ray.ang.ui.SettingsActivity
|
import kotlinx.coroutines.isActive
|
||||||
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.IOException
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.net.*
|
import java.net.*
|
||||||
import java.util.regex.Matcher
|
import kotlin.coroutines.coroutineContext
|
||||||
import java.util.regex.Pattern
|
|
||||||
import java.math.BigInteger
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import libv2ray.Libv2ray
|
|
||||||
|
|
||||||
|
|
||||||
object Utils {
|
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
|
* convert string to editalbe for kotlin
|
||||||
*
|
*
|
||||||
@@ -102,7 +89,7 @@ object Utils {
|
|||||||
try {
|
try {
|
||||||
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
val clipData = ClipData.newPlainText(null, content)
|
val clipData = ClipData.newPlainText(null, content)
|
||||||
cmb.primaryClip = clipData
|
cmb.setPrimaryClip(clipData)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -135,41 +122,31 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* get remote dns servers from preference
|
* get remote dns servers from preference
|
||||||
*/
|
*/
|
||||||
fun getRemoteDnsServers(defaultDPreference: DPreference): ArrayList<String> {
|
fun getRemoteDnsServers(): List<String> {
|
||||||
val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, AppConfig.DNS_AGENT)
|
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
|
||||||
val ret = ArrayList<String>()
|
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
|
||||||
if (!TextUtils.isEmpty(remoteDns)) {
|
if (ret.isEmpty()) {
|
||||||
remoteDns
|
return listOf(AppConfig.DNS_AGENT)
|
||||||
.split(",")
|
|
||||||
.forEach {
|
|
||||||
if (Utils.isPureIpAddress(it)) {
|
|
||||||
ret.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ret.size == 0) {
|
|
||||||
ret.add(AppConfig.DNS_AGENT)
|
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getVpnDnsServers(): List<String> {
|
||||||
|
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS)
|
||||||
|
?: settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS)
|
||||||
|
?: AppConfig.DNS_AGENT
|
||||||
|
return vpnDns.split(",").filter { isPureIpAddress(it) }
|
||||||
|
// allow empty, in that case dns will use system default
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get remote dns servers from preference
|
* get remote dns servers from preference
|
||||||
*/
|
*/
|
||||||
fun getDomesticDnsServers(defaultDPreference: DPreference): ArrayList<String> {
|
fun getDomesticDnsServers(): List<String> {
|
||||||
val domesticDns = defaultDPreference.getPrefString(SettingsActivity.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
||||||
val ret = ArrayList<String>()
|
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
|
||||||
if (!TextUtils.isEmpty(domesticDns)) {
|
if (ret.isEmpty()) {
|
||||||
domesticDns
|
return listOf(AppConfig.DNS_DIRECT)
|
||||||
.split(",")
|
|
||||||
.forEach {
|
|
||||||
if (Utils.isPureIpAddress(it)) {
|
|
||||||
ret.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ret.size == 0) {
|
|
||||||
ret.add(AppConfig.DNS_DIRECT)
|
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@@ -270,7 +247,7 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun isValidUrl(value: String?): Boolean {
|
fun isValidUrl(value: String?): Boolean {
|
||||||
try {
|
try {
|
||||||
if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} catch (e: WriterException) {
|
} catch (e: WriterException) {
|
||||||
@@ -280,76 +257,13 @@ object Utils {
|
|||||||
return false
|
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 {
|
|
||||||
if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
|
|
||||||
context.toast(R.string.toast_warning_pref_proxysharing_short)
|
|
||||||
}else{
|
|
||||||
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
|
return false
|
||||||
}
|
}
|
||||||
}
|
V2RayServiceManager.startV2Ray(context)
|
||||||
|
return true
|
||||||
/**
|
|
||||||
* startVService
|
|
||||||
*/
|
|
||||||
fun startVService(context: Context, guid: String): Boolean {
|
|
||||||
val index = AngConfigManager.getIndexViaGuid(guid)
|
|
||||||
context.v2RayApplication.curIndex=index
|
|
||||||
return startVService(context, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* startVService
|
|
||||||
*/
|
|
||||||
fun startVService(context: Context, index: Int): Boolean {
|
|
||||||
AngConfigManager.setActiveServer(index)
|
|
||||||
return startVService(context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -448,12 +362,11 @@ object Utils {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* readTextFromAssets
|
* readTextFromAssets
|
||||||
*/
|
*/
|
||||||
fun readTextFromAssets(app: AngApplication, fileName: String): String {
|
fun readTextFromAssets(context: Context, fileName: String): String {
|
||||||
val content = app.assets.open(fileName).bufferedReader().use {
|
val content = context.assets.open(fileName).bufferedReader().use {
|
||||||
it.readText()
|
it.readText()
|
||||||
}
|
}
|
||||||
return content
|
return content
|
||||||
@@ -467,7 +380,7 @@ object Utils {
|
|||||||
val command = "/system/bin/ping -c 3 $url"
|
val command = "/system/bin/ping -c 3 $url"
|
||||||
val process = Runtime.getRuntime().exec(command)
|
val process = Runtime.getRuntime().exec(command)
|
||||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||||
if (!TextUtils.isEmpty(allText)) {
|
if (allText.isNotBlank()) {
|
||||||
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
||||||
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
|
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
|
||||||
if (temps.count() > 0 && temps[0].length < 10) {
|
if (temps.count() > 0 && temps[0].length < 10) {
|
||||||
@@ -483,33 +396,51 @@ object Utils {
|
|||||||
/**
|
/**
|
||||||
* tcping
|
* tcping
|
||||||
*/
|
*/
|
||||||
fun tcping(url: String, port: Int): String {
|
suspend fun tcping(url: String, port: Int): Long {
|
||||||
var time = -1L
|
var time = -1L
|
||||||
for (k in 0 until 2) {
|
for (k in 0 until 2) {
|
||||||
val one = socketConnectTime(url, port)
|
val one = socketConnectTime(url, port)
|
||||||
if (one != -1L )
|
if (!coroutineContext.isActive) {
|
||||||
if(time == -1L || one < time) {
|
break
|
||||||
|
}
|
||||||
|
if (one != -1L && (time == -1L || one < time)) {
|
||||||
time = one
|
time = one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return time.toString() + "ms"
|
return time
|
||||||
}
|
}
|
||||||
|
|
||||||
fun socketConnectTime(url: String, port: Int): Long {
|
fun socketConnectTime(url: String, port: Int): Long {
|
||||||
try {
|
try {
|
||||||
|
val socket = Socket()
|
||||||
|
synchronized(this) {
|
||||||
|
tcpTestingSockets.add(socket)
|
||||||
|
}
|
||||||
val start = System.currentTimeMillis()
|
val start = System.currentTimeMillis()
|
||||||
val socket = Socket(url, port)
|
socket.connect(InetSocketAddress(url, port))
|
||||||
val time = System.currentTimeMillis() - start
|
val time = System.currentTimeMillis() - start
|
||||||
|
synchronized(this) {
|
||||||
|
tcpTestingSockets.remove(socket)
|
||||||
|
}
|
||||||
socket.close()
|
socket.close()
|
||||||
return time
|
return time
|
||||||
} catch (e: UnknownHostException) {
|
} catch (e: UnknownHostException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun closeAllTcpSockets() {
|
||||||
|
synchronized(this) {
|
||||||
|
tcpTestingSockets.forEach {
|
||||||
|
it?.close()
|
||||||
|
}
|
||||||
|
tcpTestingSockets.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,155 +1,123 @@
|
|||||||
package com.v2ray.ang.util
|
package com.v2ray.ang.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.gson.Gson
|
import com.google.gson.*
|
||||||
import com.google.gson.GsonBuilder
|
import com.tencent.mmkv.MMKV
|
||||||
import com.google.gson.JsonArray
|
|
||||||
import com.v2ray.ang.AngApplication
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.extension.defaultDPreference
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.ui.SettingsActivity
|
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||||
import org.json.JSONException
|
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
||||||
import org.json.JSONObject
|
|
||||||
import org.json.JSONArray
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
|
|
||||||
object V2rayConfigUtil {
|
object V2rayConfigUtil {
|
||||||
private val requestObj: JsonObject by lazy {
|
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||||
Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""", JsonObject::class.java)
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
}
|
|
||||||
|
|
||||||
// private val responseObj: JSONObject by lazy {
|
|
||||||
// JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""")
|
|
||||||
// }
|
|
||||||
|
|
||||||
data class Result(var status: Boolean, var content: String)
|
data class Result(var status: Boolean, var content: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成v2ray的客户端配置文件
|
* 生成v2ray的客户端配置文件
|
||||||
*/
|
*/
|
||||||
fun getV2rayConfig(app: AngApplication, vmess: VmessBean): Result {
|
fun getV2rayConfig(context: Context, guid: String): Result {
|
||||||
var result = Result(false, "")
|
|
||||||
try {
|
try {
|
||||||
//检查设置
|
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
|
||||||
// if (config.index < 0
|
if (config.configType == EConfigType.CUSTOM) {
|
||||||
// || config.vmess.count() <= 0
|
val raw = serverRawStorage?.decodeString(guid)
|
||||||
// || config.index > config.vmess.count() - 1
|
val customConfig = if (raw.isNullOrBlank()) {
|
||||||
// ) {
|
config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
|
||||||
// return result
|
} else {
|
||||||
// }
|
raw
|
||||||
|
}
|
||||||
if (vmess.configType == AppConfig.EConfigType.Vmess) {
|
Log.d("V2rayConfigUtilGoLog", customConfig)
|
||||||
result = getV2rayConfigType1(app, vmess)
|
return Result(true, customConfig)
|
||||||
} 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 domainName = parseDomainName(result.content)
|
val result = getV2rayNonCustomConfig(context, outbound)
|
||||||
if (!TextUtils.isEmpty(domainName)) {
|
|
||||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("V2rayConfigUtilGoLog", result.content)
|
Log.d("V2rayConfigUtilGoLog", result.content)
|
||||||
return result
|
return result
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return result
|
return Result(false, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成v2ray的客户端配置文件
|
* 生成v2ray的客户端配置文件
|
||||||
*/
|
*/
|
||||||
private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result {
|
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result {
|
||||||
val result = Result(false, "")
|
val result = Result(false, "")
|
||||||
try {
|
//取得默认配置
|
||||||
//取得默认配置
|
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
|
||||||
val assets = Utils.readTextFromAssets(app, "v2ray_config.json")
|
if (TextUtils.isEmpty(assets)) {
|
||||||
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
|
return result
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
//转成Json
|
||||||
* 生成v2ray的客户端配置文件
|
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||||
*/
|
|
||||||
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
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||||
e.printStackTrace()
|
|
||||||
return result
|
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)
|
||||||
}
|
}
|
||||||
|
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
|
||||||
|
v2rayConfig.stats = null
|
||||||
|
v2rayConfig.policy = null
|
||||||
|
}
|
||||||
|
result.status = true
|
||||||
|
result.content = v2rayConfig.toPrettyPrinting()
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
|
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
|
||||||
|
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
|
||||||
|
|
||||||
v2rayConfig.inbounds.forEach { curInbound ->
|
v2rayConfig.inbounds.forEach { curInbound ->
|
||||||
if (!app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
|
if (!(settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) ?: false)) {
|
||||||
//bind all inbounds to localhost if the user requests
|
//bind all inbounds to localhost if the user requests
|
||||||
curInbound.listen = "127.0.0.1"
|
curInbound.listen = "127.0.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v2rayConfig.inbounds[0].port = 10808
|
v2rayConfig.inbounds[0].port = 10808
|
||||||
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
|
||||||
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, ""))
|
?: false
|
||||||
|
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED)
|
||||||
|
?: true
|
||||||
|
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
|
||||||
|
if (!sniffAllTlsAndHttp) {
|
||||||
|
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
|
||||||
|
}
|
||||||
|
if (fakedns) {
|
||||||
|
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
|
||||||
|
}
|
||||||
|
|
||||||
// if (socksPort > 0) {
|
//v2rayConfig.inbounds[1].port = httpPort
|
||||||
// v2rayConfig.inbounds[0].port = socksPort
|
|
||||||
// }
|
// if (httpPort > 0) {
|
||||||
// if (lanconnPort > 0) {
|
|
||||||
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
||||||
// httpCopy.port = lanconnPort
|
// httpCopy.port = httpPort
|
||||||
// httpCopy.protocol = "http"
|
// httpCopy.protocol = "http"
|
||||||
// v2rayConfig.inbounds.add(httpCopy)
|
// v2rayConfig.inbounds.add(httpCopy)
|
||||||
// }
|
// }
|
||||||
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SNIFFING_ENABLED, true)
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
@@ -157,231 +125,39 @@ object V2rayConfigUtil {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun fakedns(v2rayConfig: V2rayConfig) {
|
||||||
* vmess协议服务器配置
|
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||||
*/
|
v2rayConfig.fakedns = V2rayConfig.FakednsBean()
|
||||||
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
|
||||||
try {
|
it.settings?.domainStrategy = "UseIP"
|
||||||
val outbound = v2rayConfig.outbounds[0]
|
|
||||||
|
|
||||||
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
|
* routing
|
||||||
*/
|
*/
|
||||||
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig)
|
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig)
|
?: "", AppConfig.TAG_AGENT, v2rayConfig)
|
||||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig)
|
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
|
?: "", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||||
|
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||||
|
?: "", AppConfig.TAG_BLOCKED, v2rayConfig)
|
||||||
|
|
||||||
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
|
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||||
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
?: "IPIfNonMatch"
|
||||||
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||||
|
|
||||||
// Hardcode googleapis.cn
|
// Hardcode googleapis.cn
|
||||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||||
type = "field",
|
type = "field",
|
||||||
outboundTag = AppConfig.TAG_AGENT,
|
outboundTag = AppConfig.TAG_AGENT,
|
||||||
domain = arrayListOf("domain:googleapis.cn")
|
domain = arrayListOf("domain:googleapis.cn")
|
||||||
)
|
)
|
||||||
|
|
||||||
when (routingMode) {
|
when (routingMode) {
|
||||||
"0" -> {
|
|
||||||
}
|
|
||||||
"1" -> {
|
"1" -> {
|
||||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||||
}
|
}
|
||||||
@@ -445,21 +221,19 @@ object V2rayConfigUtil {
|
|||||||
rulesIP.outboundTag = tag
|
rulesIP.outboundTag = tag
|
||||||
rulesIP.ip = ArrayList<String>()
|
rulesIP.ip = ArrayList<String>()
|
||||||
|
|
||||||
userRule.trim().replace("\n", "")
|
userRule.split(",").map { it.trim() }.forEach {
|
||||||
.split(",")
|
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||||
.forEach {
|
rulesIP.ip?.add(it)
|
||||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
} else if (it.isNotEmpty())
|
||||||
rulesIP.ip?.add(it)
|
|
||||||
} else if (it.isNotBlank() || it.isNotEmpty())
|
|
||||||
// if (Utils.isValidUrl(it)
|
// if (Utils.isValidUrl(it)
|
||||||
// || it.startsWith("geosite:")
|
// || it.startsWith("geosite:")
|
||||||
// || it.startsWith("regexp:")
|
// || it.startsWith("regexp:")
|
||||||
// || it.startsWith("domain:")
|
// || it.startsWith("domain:")
|
||||||
// || it.startsWith("full:"))
|
// || it.startsWith("full:"))
|
||||||
{
|
{
|
||||||
rulesDomain.domain?.add(it)
|
rulesDomain.domain?.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rulesDomain.domain?.size!! > 0) {
|
if (rulesDomain.domain?.size!! > 0) {
|
||||||
v2rayConfig.routing.rules.add(rulesDomain)
|
v2rayConfig.routing.rules.add(rulesDomain)
|
||||||
}
|
}
|
||||||
@@ -474,9 +248,8 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
private fun userRule2Domian(userRule: String): ArrayList<String> {
|
private fun userRule2Domian(userRule: String): ArrayList<String> {
|
||||||
val domain = ArrayList<String>()
|
val domain = ArrayList<String>()
|
||||||
userRule.trim().replace("\n", "").split(",").forEach {
|
userRule.split(",").map { it.trim() }.forEach {
|
||||||
if ((it.startsWith("geosite:") || it.startsWith("domain:")) &&
|
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
||||||
it.isNotBlank() && it.isNotEmpty()) {
|
|
||||||
domain.add(it)
|
domain.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -486,33 +259,39 @@ object V2rayConfigUtil {
|
|||||||
/**
|
/**
|
||||||
* Custom Dns
|
* Custom Dns
|
||||||
*/
|
*/
|
||||||
private fun customLocalDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
val hosts = mutableMapOf<String, String>()
|
val hosts = mutableMapOf<String, String>()
|
||||||
val servers = ArrayList<Any>()
|
val servers = ArrayList<Any>()
|
||||||
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference)
|
val remoteDns = Utils.getRemoteDnsServers()
|
||||||
|
val 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)))
|
||||||
|
}
|
||||||
remoteDns.forEach {
|
remoteDns.forEach {
|
||||||
servers.add(it)
|
servers.add(it)
|
||||||
}
|
}
|
||||||
|
if (proxyDomain.size > 0) {
|
||||||
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference)
|
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, proxyDomain, null))
|
||||||
|
|
||||||
val agDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
|
|
||||||
if (agDomain.size > 0) {
|
|
||||||
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain))
|
|
||||||
}
|
}
|
||||||
|
if (directDomain.size > 0) {
|
||||||
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
|
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
|
||||||
if (dirDomain.size > 0) {
|
|
||||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain))
|
|
||||||
}
|
}
|
||||||
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||||
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
|
||||||
if (routingMode == "2" || routingMode == "3") {
|
if (routingMode == "2" || routingMode == "3") {
|
||||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, arrayListOf("geosite:cn")))
|
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
|
||||||
}
|
}
|
||||||
|
|
||||||
val blkDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
|
val blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||||
|
?: "")
|
||||||
if (blkDomain.size > 0) {
|
if (blkDomain.size > 0) {
|
||||||
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
||||||
}
|
}
|
||||||
@@ -527,53 +306,57 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
// DNS inbound对象
|
// DNS inbound对象
|
||||||
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
||||||
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
||||||
address = remoteDns.first(),
|
address = if (remoteDns.first().startsWith("https")) "1.1.1.1" else remoteDns.first(),
|
||||||
port = 53,
|
port = 53,
|
||||||
network = "tcp,udp")
|
network = "tcp,udp")
|
||||||
|
|
||||||
|
//val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT) ?: AppConfig.PORT_LOCAL_DNS)
|
||||||
v2rayConfig.inbounds.add(
|
v2rayConfig.inbounds.add(
|
||||||
V2rayConfig.InboundBean(
|
V2rayConfig.InboundBean(
|
||||||
tag = "dns-in",
|
tag = "dns-in",
|
||||||
port = 10807,
|
port = 10807,
|
||||||
listen = "127.0.0.1",
|
listen = "127.0.0.1",
|
||||||
protocol = "dokodemo-door",
|
protocol = "dokodemo-door",
|
||||||
settings = dnsInboundSettings,
|
settings = dnsInboundSettings,
|
||||||
sniffing = null))
|
sniffing = null))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS outbound对象
|
// DNS outbound对象
|
||||||
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
|
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
|
||||||
v2rayConfig.outbounds.add(
|
v2rayConfig.outbounds.add(
|
||||||
V2rayConfig.OutboundBean(
|
V2rayConfig.OutboundBean(
|
||||||
protocol = "dns",
|
protocol = "dns",
|
||||||
tag = "dns-out",
|
tag = "dns-out",
|
||||||
settings = null,
|
settings = null,
|
||||||
streamSettings = null,
|
streamSettings = null,
|
||||||
mux = null))
|
mux = null))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DNS routing
|
// DNS routing
|
||||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
if (!domesticDns.first().startsWith("https")) {
|
||||||
type = "field",
|
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
outboundTag = AppConfig.TAG_DIRECT,
|
type = "field",
|
||||||
port = "53",
|
outboundTag = AppConfig.TAG_DIRECT,
|
||||||
ip = domesticDns,
|
port = "53",
|
||||||
domain = null)
|
ip = arrayListOf(domesticDns.first()),
|
||||||
)
|
domain = null)
|
||||||
|
)
|
||||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
}
|
||||||
type = "field",
|
if (!remoteDns.first().startsWith("https")) {
|
||||||
outboundTag = AppConfig.TAG_AGENT,
|
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
port = "53",
|
type = "field",
|
||||||
ip = remoteDns,
|
outboundTag = AppConfig.TAG_AGENT,
|
||||||
domain = null)
|
port = "53",
|
||||||
)
|
ip = arrayListOf(remoteDns.first()),
|
||||||
|
domain = null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// DNS routing tag
|
// DNS routing tag
|
||||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
type = "field",
|
type = "field",
|
||||||
inboundTag = arrayListOf<String>("dns-in"),
|
inboundTag = arrayListOf("dns-in"),
|
||||||
outboundTag = "dns-out",
|
outboundTag = "dns-out",
|
||||||
domain = null)
|
domain = null)
|
||||||
)
|
)
|
||||||
@@ -588,15 +371,18 @@ object V2rayConfigUtil {
|
|||||||
/**
|
/**
|
||||||
* Custom Remote Dns
|
* Custom Remote Dns
|
||||||
*/
|
*/
|
||||||
private fun customRemoteDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
private fun customRemoteDns(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
val servers = ArrayList<Any>()
|
val servers = ArrayList<Any>()
|
||||||
|
val hosts = mutableMapOf<String, String>()
|
||||||
|
|
||||||
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
|
Utils.getRemoteDnsServers().forEach {
|
||||||
servers.add(it)
|
servers.add(it)
|
||||||
}
|
}
|
||||||
|
// hardcode googleapi rule to fix play store problems
|
||||||
|
hosts.put("domain:googleapis.cn", "googleapis.com")
|
||||||
|
|
||||||
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers)
|
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers, hosts = hosts)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return false
|
return false
|
||||||
@@ -604,80 +390,26 @@ object V2rayConfigUtil {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean {
|
||||||
* is valid config
|
|
||||||
*/
|
|
||||||
fun isValidConfig(conf: String): Boolean {
|
|
||||||
try {
|
try {
|
||||||
val jObj = JSONObject(conf)
|
if (outbound.streamSettings?.network == DEFAULT_NETWORK
|
||||||
var hasBound = false
|
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
|
||||||
//hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound"))
|
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
|
||||||
hasBound = (jObj.has("outbounds")) or (jObj.has("outbound"))
|
val Host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
||||||
return hasBound
|
|
||||||
} catch (e: JSONException) {
|
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()
|
||||||
return false
|
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 ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
V2rayNG/app/src/main/res/drawable/background_test_button.xml
Normal file
10
V2rayNG/app/src/main/res/drawable/background_test_button.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?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>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?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>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?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>
|
||||||
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_direct.png
Normal file
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_direct.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_proxy.png
Normal file
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_proxy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -48,14 +48,17 @@
|
|||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1" />
|
android:layout_weight="1"
|
||||||
|
android:nextFocusRight="@+id/fab" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_test"
|
android:id="@+id/layout_test"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/connection_test_height"
|
android:layout_height="@dimen/connection_test_height"
|
||||||
android:background="@color/colorPrimary_text"
|
android:background="@drawable/background_test_button"
|
||||||
android:gravity="center|left">
|
android:gravity="center|left"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_test_state"
|
android:id="@+id/tv_test_state"
|
||||||
@@ -92,7 +95,10 @@
|
|||||||
android:layout_marginLeft="16dp"
|
android:layout_marginLeft="16dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:src="@drawable/ic_v_idle"
|
android:src="@drawable/ic_v_idle"
|
||||||
app:layout_anchorGravity="bottom|right|end" />
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:layout_anchorGravity="bottom|right|end"
|
||||||
|
android:nextFocusLeft="@+id/recycler_view" />
|
||||||
|
|
||||||
</com.github.jorgecastilloprz.FABProgressCircle>
|
</com.github.jorgecastilloprz.FABProgressCircle>
|
||||||
</android.support.design.widget.CoordinatorLayout>
|
</android.support.design.widget.CoordinatorLayout>
|
||||||
@@ -107,7 +113,23 @@
|
|||||||
app:headerLayout="@layout/nav_header"
|
app:headerLayout="@layout/nav_header"
|
||||||
app:itemIconTint="@color/colorPrimary_dark"
|
app:itemIconTint="@color/colorPrimary_dark"
|
||||||
app:itemTextColor="@color/colorPrimary"
|
app:itemTextColor="@color/colorPrimary"
|
||||||
app:menu="@menu/menu_drawer" />
|
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>
|
||||||
|
|
||||||
</android.support.v4.widget.DrawerLayout>
|
</android.support.v4.widget.DrawerLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -80,11 +80,51 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
android:orientation="vertical">
|
||||||
android:orientation="vertical" />
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="User(Optional)" />
|
||||||
|
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_security"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/edit_height"
|
||||||
|
android:inputType="text" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Password(Optional)" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_id"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/edit_height"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/layout_margin_top_height"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -143,28 +143,28 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_network" />
|
android:text="@string/server_lab_more_function"
|
||||||
|
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/server_lab_network" />
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/sp_network"
|
android:id="@+id/sp_network"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/edit_height"
|
android:layout_height="@dimen/edit_height"
|
||||||
android:entries="@array/networks" />
|
android:entries="@array/networks" />
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/layout_margin_top_height"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/server_lab_more_function"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -174,6 +174,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/sp_header_type_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/server_lab_head_type" />
|
android:text="@string/server_lab_head_type" />
|
||||||
@@ -181,8 +182,7 @@
|
|||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/sp_header_type"
|
android:id="@+id/sp_header_type"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="@dimen/edit_height"
|
android:layout_height="@dimen/edit_height" />
|
||||||
android:entries="@array/headertypes" />
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -241,6 +241,28 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/layout_margin_top_height"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/server_lab_allow_insecure" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/sp_allow_insecure"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/edit_height"
|
||||||
|
android:entries="@array/allowinsecures" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -249,4 +271,4 @@
|
|||||||
android:orientation="vertical" />
|
android:orientation="vertical" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
<android.support.v7.widget.AppCompatImageView
|
<android.support.v7.widget.AppCompatImageView
|
||||||
android:id="@+id/icon"
|
android:id="@+id/icon"
|
||||||
@@ -40,6 +43,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
android:paddingStart="2dp"
|
android:paddingStart="2dp"
|
||||||
android:paddingLeft="2dp"
|
android:paddingLeft="2dp"
|
||||||
android:paddingEnd="6dp"
|
android:paddingEnd="6dp"
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/item_bg"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/item_bg"
|
||||||
android:gravity="center_vertical">
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
<android.support.v7.widget.CardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="3dp"
|
android:layout_margin="3dp"
|
||||||
android:clickable="true"
|
app:cardCornerRadius="5dp">
|
||||||
android:focusable="true"
|
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
card_view:cardCornerRadius="5dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/info_container"
|
android:id="@+id/info_container"
|
||||||
@@ -21,31 +19,20 @@
|
|||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="@dimen/server_height"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:nextFocusRight="@+id/layout_share">
|
||||||
|
|
||||||
<LinearLayout
|
<android.support.v7.widget.AppCompatRadioButton
|
||||||
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:id="@+id/btn_radio"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:focusableInTouchMode="false" />
|
android:focusableInTouchMode="false" />
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -85,20 +72,31 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="right"
|
android:orientation="horizontal"
|
||||||
android:orientation="vertical"
|
android:paddingEnd="5dp">
|
||||||
android:paddingEnd="5dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tv_test_result"
|
android:id="@+id/tv_subscription"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:lines="1"
|
android:layout_weight="1"
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
android:lines="1"
|
||||||
android:textColor="@color/colorPing"
|
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||||
android:textSize="10sp" />
|
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" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -111,10 +109,14 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_share"
|
android:id="@+id/layout_share"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
android:padding="@dimen/layout_margin_spacing"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:nextFocusLeft="@+id/info_container">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -126,10 +128,13 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_edit"
|
android:id="@+id/layout_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
android:padding="@dimen/layout_margin_spacing"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
@@ -141,10 +146,13 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_remove"
|
android:id="@+id/layout_remove"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="match_parent"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
android:padding="@dimen/layout_margin_spacing"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
@@ -175,4 +183,4 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</android.support.v7.widget.CardView>
|
</android.support.v7.widget.CardView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
<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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="3dp"
|
android:layout_margin="3dp"
|
||||||
@@ -13,7 +14,8 @@
|
|||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:foreground="?android:attr/selectableItemBackground"
|
android:foreground="?android:attr/selectableItemBackground"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
card_view:cardCornerRadius="5dp">
|
card_view:cardCornerRadius="5dp"
|
||||||
|
android:nextFocusRight="@+id/layout_edit">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/info_container"
|
android:id="@+id/info_container"
|
||||||
@@ -62,11 +64,14 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_share"
|
android:id="@+id/layout_share"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingEnd="@dimen/layout_margin_right_height"
|
android:padding="@dimen/layout_margin_spacing"
|
||||||
android:visibility="invisible">
|
android:visibility="invisible"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
@@ -78,10 +83,14 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layout_edit"
|
android:id="@+id/layout_edit"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="@dimen/server_height"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
android:padding="@dimen/layout_margin_spacing"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:nextFocusLeft="@+id/item_cardview">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="@dimen/png_height"
|
android:layout_width="@dimen/png_height"
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?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,12 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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:id="@+id/layout_switch"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -14,16 +7,9 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:layout_width="40dp"
|
android:layout_width="40dp"
|
||||||
android:layout_height="40dp"
|
android:layout_height="40dp"
|
||||||
android:src="@mipmap/ic_launcher" />
|
android:padding="10dp"
|
||||||
|
android:src="@drawable/ic_v" />
|
||||||
<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>
|
</LinearLayout>
|
||||||
@@ -4,12 +4,7 @@
|
|||||||
tools:showIn="navigation_view">
|
tools:showIn="navigation_view">
|
||||||
|
|
||||||
<group
|
<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
|
<item
|
||||||
android:id="@+id/sub_setting"
|
android:id="@+id/sub_setting"
|
||||||
android:icon="@drawable/ic_subscriptions_white_24dp"
|
android:icon="@drawable/ic_subscriptions_white_24dp"
|
||||||
@@ -21,25 +16,27 @@
|
|||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<item android:title="@string/title_about">
|
<group android:id="@+id/group_id2">
|
||||||
<menu>
|
<item
|
||||||
<item
|
|
||||||
android:id="@+id/promotion"
|
android:id="@+id/promotion"
|
||||||
android:icon="@drawable/ic_whatshot_white_24dp"
|
android:icon="@drawable/ic_whatshot_white_24dp"
|
||||||
android:title="@string/title_pref_promotion" />
|
android:title="@string/title_pref_promotion" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/donate"
|
android:id="@+id/donate"
|
||||||
android:icon="@drawable/ic_attach_money_white_24dp"
|
android:icon="@drawable/ic_attach_money_white_24dp"
|
||||||
android:title="@string/title_pref_donate" />
|
android:title="@string/title_pref_donate" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/logcat"
|
android:id="@+id/logcat"
|
||||||
android:icon="@drawable/ic_logcat_white_24dp"
|
android:icon="@drawable/ic_logcat_white_24dp"
|
||||||
android:title="@string/title_logcat" />
|
android:title="@string/title_logcat" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/feedback"
|
android:id="@+id/feedback"
|
||||||
android:icon="@drawable/ic_feedback_white_24dp"
|
android:icon="@drawable/ic_feedback_white_24dp"
|
||||||
android:title="@string/title_pref_feedback" />
|
android:title="@string/title_pref_feedback" />
|
||||||
</menu>
|
<!-- place holder for version text at the bottom -->
|
||||||
</item>
|
<item
|
||||||
|
android:id="@+id/placeholder"
|
||||||
|
android:enabled="false"
|
||||||
|
android:title="" />
|
||||||
|
</group>
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -6,4 +6,9 @@
|
|||||||
android:icon="@drawable/ic_copy_white"
|
android:icon="@drawable/ic_copy_white"
|
||||||
android:title="@string/logcat_copy"
|
android:title="@string/logcat_copy"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/delete"
|
||||||
|
android:icon="@drawable/ic_delete_white_24dp"
|
||||||
|
android:title="@string/logcat_delete"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
</menu>
|
</menu>
|
||||||
@@ -17,17 +17,6 @@
|
|||||||
<copyright>Copyright(C) 2008-2011 The Android Open Source Project</copyright>
|
<copyright>Copyright(C) 2008-2011 The Android Open Source Project</copyright>
|
||||||
<license>Apache Software License 2.0</license>
|
<license>Apache Software License 2.0</license>
|
||||||
</notice>
|
</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>
|
<notice>
|
||||||
<name>Google Gson</name>
|
<name>Google Gson</name>
|
||||||
<url>https://github.com/google/gson</url>
|
<url>https://github.com/google/gson</url>
|
||||||
@@ -52,33 +41,9 @@
|
|||||||
<copyright>Copyright 2015 Square, Inc.</copyright>
|
<copyright>Copyright 2015 Square, Inc.</copyright>
|
||||||
<license>Apache Software License 2.0</license>
|
<license>Apache Software License 2.0</license>
|
||||||
</notice>
|
</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>
|
<notice>
|
||||||
<name>RxPermissions</name>
|
<name>RxPermissions</name>
|
||||||
<url>https://github.com/tbruyelle/RxPermissions</url>
|
<url>https://github.com/tbruyelle/RxPermissions</url>
|
||||||
<license>Apache Software License 2.0</license>
|
<license>Apache Software License 2.0</license>
|
||||||
</notice>
|
</notice>
|
||||||
<notice>
|
</notices>
|
||||||
<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>
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?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,9 +3,11 @@
|
|||||||
<string name="app_name">v2rayNG</string>
|
<string name="app_name">v2rayNG</string>
|
||||||
<string name="app_widget_name">开关</string>
|
<string name="app_widget_name">开关</string>
|
||||||
<string name="app_tile_name">开关</string>
|
<string name="app_tile_name">开关</string>
|
||||||
<string name="app_tile_first_use">初次使用此功能请先用APP激活VPN</string>
|
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
|
||||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||||
|
<string name="migration_success">数据迁移成功!</string>
|
||||||
|
<string name="migration_fail">数据迁移失败啦!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">停止</string>
|
<string name="notification_action_stop_v2ray">停止</string>
|
||||||
@@ -41,10 +43,11 @@
|
|||||||
<string name="server_lab_network">传输协议(network)</string>
|
<string name="server_lab_network">传输协议(network)</string>
|
||||||
<string name="server_lab_more_function">功能设置(不清楚则保持默认值)</string>
|
<string name="server_lab_more_function">功能设置(不清楚则保持默认值)</string>
|
||||||
<string name="server_lab_head_type">伪装类型(type)</string>
|
<string name="server_lab_head_type">伪装类型(type)</string>
|
||||||
<string name="server_lab_request_host">伪装域名host(host/ws host/h2 host)/QUIC 加密方式</string>
|
<string name="server_lab_mode_type">gRPC 传输模式 (mode)</string>
|
||||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥</string>
|
<string name="server_lab_request_host">伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||||
<string name="server_lab_stream_security">底层传输安全</string>
|
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName</string>
|
||||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
<string name="server_lab_stream_security">底层传输安全(tls)</string>
|
||||||
|
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||||
<string name="server_lab_address3">服务器地址</string>
|
<string name="server_lab_address3">服务器地址</string>
|
||||||
<string name="server_lab_port3">服务器端口</string>
|
<string name="server_lab_port3">服务器端口</string>
|
||||||
<string name="server_lab_id3">密码</string>
|
<string name="server_lab_id3">密码</string>
|
||||||
@@ -73,9 +76,8 @@
|
|||||||
|
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">设置</string>
|
<string name="title_settings">设置</string>
|
||||||
<string name="title_about">关于</string>
|
|
||||||
<string name="title_advanced">进阶设置</string>
|
<string name="title_advanced">进阶设置</string>
|
||||||
|
<string name="title_vpn_settings">VPN 设置</string>
|
||||||
<string name="title_pref_per_app_proxy">分应用代理</string>
|
<string name="title_pref_per_app_proxy">分应用代理</string>
|
||||||
<string name="summary_pref_per_app_proxy">分应用代理仅支持 Android 5.0 Lollipop 及更高</string>
|
<string name="summary_pref_per_app_proxy">分应用代理仅支持 Android 5.0 Lollipop 及更高</string>
|
||||||
|
|
||||||
@@ -83,13 +85,16 @@
|
|||||||
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
|
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
|
||||||
|
|
||||||
<string name="title_pref_speed_enabled">启用速度显示</string>
|
<string name="title_pref_speed_enabled">启用速度显示</string>
|
||||||
<string name="summary_pref_speed_enabled">在通知中显示当前速度</string>
|
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
|
||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">启用流量探测</string>
|
<string name="title_pref_sniffing_enabled">启用流量探测</string>
|
||||||
<string name="summary_pref_sniffing_enabled">流量探测</string>
|
<string name="summary_pref_sniffing_enabled">从流量中探测域名 (默认启用)</string>
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">启用本地DNS</string>
|
<string name="title_pref_local_dns_enabled">启用本地DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS由本地设备的v2Ray-core处理(测试)</string>
|
<string name="summary_pref_local_dns_enabled">DNS 请求导入 core 由 DNS 模块处理(推荐启用 如果需要路由绕过局域网及大陆地址)</string>
|
||||||
|
|
||||||
|
<string name="title_pref_fake_dns_enabled">启用虚拟DNS</string>
|
||||||
|
<string name="summary_pref_fake_dns_enabled">本地返回虚构解析结果 (减低延时 但个别应用可能无法使用)</string>
|
||||||
|
|
||||||
<string name="title_pref_forward_ipv6">IPv6 路由</string>
|
<string name="title_pref_forward_ipv6">IPv6 路由</string>
|
||||||
<string name="summary_pref_forward_ipv6">转发 IPv6 流量到远程服务器(测试)</string>
|
<string name="summary_pref_forward_ipv6">转发 IPv6 流量到远程服务器(测试)</string>
|
||||||
@@ -106,7 +111,9 @@
|
|||||||
<string name="title_pref_remote_dns">远程DNS (可选)</string>
|
<string name="title_pref_remote_dns">远程DNS (可选)</string>
|
||||||
<string name="summary_pref_remote_dns">DNS</string>
|
<string name="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">境内DNS (可选,本地DNS模式下生效)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
|
||||||
|
|
||||||
|
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_socks_port">SOCKS5代理端口</string>
|
<string name="title_pref_socks_port">SOCKS5代理端口</string>
|
||||||
@@ -115,6 +122,9 @@
|
|||||||
<string name="title_pref_http_port">HTTP代理端口</string>
|
<string name="title_pref_http_port">HTTP代理端口</string>
|
||||||
<string name="summary_pref_http_port">HTTP代理端口</string>
|
<string name="summary_pref_http_port">HTTP代理端口</string>
|
||||||
|
|
||||||
|
<string name="title_pref_local_dns_port">本地DNS端口</string>
|
||||||
|
<string name="summary_pref_local_dns_port">本地DNS端口</string>
|
||||||
|
|
||||||
<string name="title_pref_feedback">反馈</string>
|
<string name="title_pref_feedback">反馈</string>
|
||||||
<string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
|
<string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
|
||||||
<string name="summary_pref_tg_group">加入Telegram Group</string>
|
<string name="summary_pref_tg_group">加入Telegram Group</string>
|
||||||
@@ -123,7 +133,8 @@
|
|||||||
<string name="title_pref_promotion">推广</string>
|
<string name="title_pref_promotion">推广</string>
|
||||||
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
|
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
|
||||||
|
|
||||||
<string name="title_pref_version">版本</string>
|
<string name="title_mode">模式</string>
|
||||||
|
<string name="title_mode_help">点此查看更多帮助</string>
|
||||||
|
|
||||||
<string name="donate_error_setup">初始化错误:</string>
|
<string name="donate_error_setup">初始化错误:</string>
|
||||||
<string name="donate_error_inventory">无法查询到项目</string>
|
<string name="donate_error_inventory">无法查询到项目</string>
|
||||||
@@ -135,7 +146,8 @@
|
|||||||
|
|
||||||
<string name="title_logcat">Logcat</string>
|
<string name="title_logcat">Logcat</string>
|
||||||
<string name="logcat_copy">复制</string>
|
<string name="logcat_copy">复制</string>
|
||||||
<string name="title_export_all">导出全部配置至剪贴板</string>
|
<string name="logcat_delete">删除</string>
|
||||||
|
<string name="title_export_all">导出全部(非自定义)配置至剪贴板</string>
|
||||||
<string name="title_sub_setting">订阅设置</string>
|
<string name="title_sub_setting">订阅设置</string>
|
||||||
<string name="sub_setting_remarks">备注</string>
|
<string name="sub_setting_remarks">备注</string>
|
||||||
<string name="sub_setting_url">地址(url)</string>
|
<string name="sub_setting_url">地址(url)</string>
|
||||||
@@ -186,4 +198,9 @@
|
|||||||
<string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string>
|
<string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string>
|
||||||
<string name="toast_malformed_josn">配置格式错误</string>
|
<string name="toast_malformed_josn">配置格式错误</string>
|
||||||
|
|
||||||
|
<string-array name="mode_entries">
|
||||||
|
<item>VPN</item>
|
||||||
|
<item>仅代理</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
<string name="app_name">v2rayNG</string>
|
<string name="app_name">v2rayNG</string>
|
||||||
<string name="app_widget_name">切換</string>
|
<string name="app_widget_name">切換</string>
|
||||||
<string name="app_tile_name">切換</string>
|
<string name="app_tile_name">切換</string>
|
||||||
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式來啟用 VPN</string>
|
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
|
||||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||||
|
<string name="migration_success">數據遷移成功!</string>
|
||||||
|
<string name="migration_fail">數據遷移失敗啦!</string>
|
||||||
|
|
||||||
<!-- Notifications -->
|
<!-- Notifications -->
|
||||||
<string name="notification_action_stop_v2ray">停止</string>
|
<string name="notification_action_stop_v2ray">停止</string>
|
||||||
@@ -17,7 +19,7 @@
|
|||||||
<string name="toast_services_failure">啟動服務失敗</string>
|
<string name="toast_services_failure">啟動服務失敗</string>
|
||||||
|
|
||||||
<!--ServerActivity-->
|
<!--ServerActivity-->
|
||||||
<string name="title_server">組態</string>
|
<string name="title_server">設定檔</string>
|
||||||
<string name="menu_item_add_config">新增組態</string>
|
<string name="menu_item_add_config">新增組態</string>
|
||||||
<string name="menu_item_save_config">儲存組態</string>
|
<string name="menu_item_save_config">儲存組態</string>
|
||||||
<string name="menu_item_del_config">刪除組態</string>
|
<string name="menu_item_del_config">刪除組態</string>
|
||||||
@@ -41,10 +43,11 @@
|
|||||||
<string name="server_lab_network">網路</string>
|
<string name="server_lab_network">網路</string>
|
||||||
<string name="server_lab_more_function">更多功能</string>
|
<string name="server_lab_more_function">更多功能</string>
|
||||||
<string name="server_lab_head_type">標頭類型</string>
|
<string name="server_lab_head_type">標頭類型</string>
|
||||||
<string name="server_lab_request_host">要求主機(host/ws host/h2 host)/QUIC加密方式</string>
|
<string name="server_lab_mode_type">gRPC 傳輸模式 (mode)</string>
|
||||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰</string>
|
<string name="server_lab_request_host">要求主機(host)(host/ws host/h2 host)/QUIC加密方式</string>
|
||||||
<string name="server_lab_stream_security">傳輸層安全性</string>
|
<string name="server_lab_path">path(ws path/h2 path)/QUIC加密密鑰/kcp seed/gRPC serviceName</string>
|
||||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
<string name="server_lab_stream_security">傳輸層安全性(tls)</string>
|
||||||
|
<string name="server_lab_allow_insecure">跳過證書驗證(allowInsecure)</string>
|
||||||
<string name="server_lab_address3">伺服器位址</string>
|
<string name="server_lab_address3">伺服器位址</string>
|
||||||
<string name="server_lab_port3">伺服器埠</string>
|
<string name="server_lab_port3">伺服器埠</string>
|
||||||
<string name="server_lab_id3">密碼</string>
|
<string name="server_lab_id3">密碼</string>
|
||||||
@@ -57,7 +60,7 @@
|
|||||||
<string name="title_file_chooser">選取一個設定檔</string>
|
<string name="title_file_chooser">選取一個設定檔</string>
|
||||||
<string name="toast_require_file_manager">請安裝檔案總管。</string>
|
<string name="toast_require_file_manager">請安裝檔案總管。</string>
|
||||||
<string name="server_customize_config">自訂組態</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="server_lab_content">內容</string>
|
||||||
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
|
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
|
||||||
<string name="toast_invalid_url">網址無效</string>
|
<string name="toast_invalid_url">網址無效</string>
|
||||||
@@ -74,9 +77,8 @@
|
|||||||
|
|
||||||
<!-- Preferences -->
|
<!-- Preferences -->
|
||||||
<string name="title_settings">設定</string>
|
<string name="title_settings">設定</string>
|
||||||
<string name="title_about">關於</string>
|
|
||||||
<string name="title_advanced">進階設定</string>
|
<string name="title_advanced">進階設定</string>
|
||||||
|
<string name="title_vpn_settings">VPN 設定</string>
|
||||||
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
|
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
|
||||||
<string name="summary_pref_per_app_proxy">Proxy 個別應用程式模式只支援 Android 5.0 (Lollipop) 或更高</string>
|
<string name="summary_pref_per_app_proxy">Proxy 個別應用程式模式只支援 Android 5.0 (Lollipop) 或更高</string>
|
||||||
|
|
||||||
@@ -84,13 +86,16 @@
|
|||||||
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
|
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
|
||||||
|
|
||||||
<string name="title_pref_speed_enabled">啟用速度顯示</string>
|
<string name="title_pref_speed_enabled">啟用速度顯示</string>
|
||||||
<string name="summary_pref_speed_enabled">在通知中顯示當前速度</string>
|
<string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖標顯示流量的路由情況</string>
|
||||||
|
|
||||||
<string name="title_pref_sniffing_enabled">啟用流量探測</string>
|
<string name="title_pref_sniffing_enabled">啟用流量探測</string>
|
||||||
<string name="summary_pref_sniffing_enabled">流量探測</string>
|
<string name="summary_pref_sniffing_enabled">從流量中探測域名 (默認啟用)</string>
|
||||||
|
|
||||||
<string name="title_pref_local_dns_enabled">啟用本地DNS</string>
|
<string name="title_pref_local_dns_enabled">啟用本地DNS</string>
|
||||||
<string name="summary_pref_local_dns_enabled">DNS請求由本地的v2ray core處理(測試)</string>
|
<string name="summary_pref_local_dns_enabled">DNS 請求導入 core 由 DNS 模塊處理(推薦啟用 如果需要路由略過局域網及中國大陸)</string>
|
||||||
|
|
||||||
|
<string name="title_pref_fake_dns_enabled">啟用虛擬DNS</string>
|
||||||
|
<string name="summary_pref_fake_dns_enabled">本地返回虛構解析結果 (減低延時 但個別應用可能無法使用)</string>
|
||||||
|
|
||||||
<string name="title_pref_forward_ipv6">IPv6 路由</string>
|
<string name="title_pref_forward_ipv6">IPv6 路由</string>
|
||||||
<string name="summary_pref_forward_ipv6">向遠端重新導向 IPv6 流量(測試)</string>
|
<string name="summary_pref_forward_ipv6">向遠端重新導向 IPv6 流量(測試)</string>
|
||||||
@@ -104,18 +109,22 @@
|
|||||||
<string name="summary_pref_donate">向開發人員捐款</string>
|
<string name="summary_pref_donate">向開發人員捐款</string>
|
||||||
<string name="donate_detail">新增一些實驗性進階功能</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="summary_pref_remote_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_domestic_dns">境内DNS (可选,僅本地DNS模式下生效)</string>
|
<string name="title_pref_vpn_dns">VPN DNS (僅支持 IPv4/v6)</string>
|
||||||
|
|
||||||
|
<string name="title_pref_domestic_dns">境内DNS (可選)</string>
|
||||||
<string name="summary_pref_domestic_dns">DNS</string>
|
<string name="summary_pref_domestic_dns">DNS</string>
|
||||||
|
|
||||||
<string name="title_pref_socks_port">SOCKS5代理端口</string>
|
<string name="title_pref_socks_port">SOCKS5代理連接埠</string>
|
||||||
<string name="summary_pref_socks_port">SOCKS5代理端口</string>
|
<string name="summary_pref_socks_port">SOCKS5代理連接埠</string>
|
||||||
|
|
||||||
<string name="title_pref_http_port">HTTP代理端口</string>
|
<string name="title_pref_http_port">HTTP代理連接埠</string>
|
||||||
<string name="summary_pref_http_port">HTTP代理端口</string>
|
<string name="summary_pref_http_port">HTTP代理連接埠</string>
|
||||||
|
|
||||||
|
<string name="title_pref_local_dns_port">本地DNS端口</string>
|
||||||
|
<string name="summary_pref_local_dns_port">本地DNS端口</string>
|
||||||
|
|
||||||
<string name="title_pref_feedback">回饋</string>
|
<string name="title_pref_feedback">回饋</string>
|
||||||
<string name="summary_pref_feedback">回饋提升或前往 GitHub 回報 Bug</string>
|
<string name="summary_pref_feedback">回饋提升或前往 GitHub 回報 Bug</string>
|
||||||
@@ -125,7 +134,8 @@
|
|||||||
<string name="title_pref_promotion">推廣</string>
|
<string name="title_pref_promotion">推廣</string>
|
||||||
<string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string>
|
<string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string>
|
||||||
|
|
||||||
<string name="title_pref_version">版本</string>
|
<string name="title_mode">模式</string>
|
||||||
|
<string name="title_mode_help">點此查看更多幫助</string>
|
||||||
|
|
||||||
<string name="donate_error_setup">錯誤設定:</string>
|
<string name="donate_error_setup">錯誤設定:</string>
|
||||||
<string name="donate_error_inventory">Error querying inventory</string>
|
<string name="donate_error_inventory">Error querying inventory</string>
|
||||||
@@ -137,7 +147,8 @@
|
|||||||
|
|
||||||
<string name="title_logcat">Logcat</string>
|
<string name="title_logcat">Logcat</string>
|
||||||
<string name="logcat_copy">複製</string>
|
<string name="logcat_copy">複製</string>
|
||||||
<string name="title_export_all">匯出全部配置至剪貼簿</string>
|
<string name="logcat_delete">刪除</string>
|
||||||
|
<string name="title_export_all">匯出全部(非自訂)配置至剪貼簿</string>
|
||||||
<string name="title_sub_setting">訂閱設定</string>
|
<string name="title_sub_setting">訂閱設定</string>
|
||||||
<string name="sub_setting_remarks">備註</string>
|
<string name="sub_setting_remarks">備註</string>
|
||||||
<string name="sub_setting_url">位址(url)</string>
|
<string name="sub_setting_url">位址(url)</string>
|
||||||
@@ -153,7 +164,7 @@
|
|||||||
<string name="routing_settings_delete">清除</string>
|
<string name="routing_settings_delete">清除</string>
|
||||||
<string name="routing_settings_scan_replace">掃描並取代</string>
|
<string name="routing_settings_scan_replace">掃描並取代</string>
|
||||||
<string name="routing_settings_scan_append">掃描並附加</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_pending">"檢查連線能力"</string>
|
||||||
<string name="connection_test_testing">"測試中……"</string>
|
<string name="connection_test_testing">"測試中……"</string>
|
||||||
@@ -178,13 +189,19 @@
|
|||||||
|
|
||||||
<string-array name="routing_mode">
|
<string-array name="routing_mode">
|
||||||
<item>全球</item>
|
<item>全球</item>
|
||||||
<item>略過局域網</item>
|
<item>略過區域網路</item>
|
||||||
<item>略過中國大陸</item>
|
<item>略過中國大陸</item>
|
||||||
<item>略過局域網及中國大陸</item>
|
<item>略過區域網路及中國大陸</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string name="title_pref_proxy_sharing_enabled">代理共享</string>
|
<string name="title_pref_proxy_sharing_enabled">代理共享</string>
|
||||||
<string name="summary_pref_proxy_sharing_enabled">綁定代理入口ip到0.0.0.0</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">其他設備可以使用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_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string>
|
||||||
<string name="toast_malformed_josn">配置格式錯誤</string>
|
<string name="toast_malformed_josn">配置格式錯誤</string>
|
||||||
|
|
||||||
|
<string-array name="mode_entries">
|
||||||
|
<item>VPN</item>
|
||||||
|
<item>僅代理</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user