Compare commits
188 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3923b379a6 | ||
|
|
e23e9c48a4 | ||
|
|
1822613985 | ||
|
|
e8ec9ad17c | ||
|
|
b4d970552e | ||
|
|
c6579556c4 | ||
|
|
5a36844036 | ||
|
|
2b21203c53 | ||
|
|
a4558cf954 | ||
|
|
5c2f11fb19 | ||
|
|
d7f3d0df80 | ||
|
|
c5e2ca0d8d | ||
|
|
252bea2432 | ||
|
|
f58ed74a4a | ||
|
|
8ed17f9da0 | ||
|
|
1bbfda64fe | ||
|
|
5cadef8b2a | ||
|
|
5b92158353 | ||
|
|
73706c1d0f | ||
|
|
c633a267ff | ||
|
|
c8e5bf4f9f | ||
|
|
bb91b3baa9 | ||
|
|
35dc8d661c | ||
|
|
f61f30fdc4 | ||
|
|
a54c327a07 | ||
|
|
87d2854fb2 | ||
|
|
e9b1052ef7 | ||
|
|
c6560e9bc0 | ||
|
|
a44ca16aa7 | ||
|
|
b28c7f54ff | ||
|
|
61a155b799 | ||
|
|
10cc117e81 | ||
|
|
ce9bed2e1f | ||
|
|
bb8f7de6eb | ||
|
|
a1ed4836c7 | ||
|
|
2d5351ec9e | ||
|
|
d9fb121d67 | ||
|
|
62d0951a24 | ||
|
|
d6605cc866 | ||
|
|
1fc493d879 | ||
|
|
bdc27dd180 | ||
|
|
e257c4cb56 | ||
|
|
e9d2ed98af | ||
|
|
901a82eb54 | ||
|
|
26cc29944b | ||
|
|
7052546f2b | ||
|
|
ae2b10ede7 | ||
|
|
0df051e640 | ||
|
|
79aa86a402 | ||
|
|
7bf32c2b30 | ||
|
|
ceb1c55e49 | ||
|
|
4ab3134eac | ||
|
|
0391685d42 | ||
|
|
6482253697 | ||
|
|
c033358126 | ||
|
|
0b52c78b72 | ||
|
|
a2a7d790e7 | ||
|
|
c7c6564624 | ||
|
|
f7d534fc00 | ||
|
|
ecb1d58e7a | ||
|
|
54a191d181 | ||
|
|
90b6979f94 | ||
|
|
5daf4886b7 | ||
|
|
dbee2085fb | ||
|
|
3c6c4ca3a5 | ||
|
|
3932ee6e9b | ||
|
|
242c96a4de | ||
|
|
a23245435f | ||
|
|
57476290d3 | ||
|
|
c5617ac65a | ||
|
|
3795348b04 | ||
|
|
50bf9c8da0 | ||
|
|
78fe4e4bc4 | ||
|
|
962519854d | ||
|
|
841629f9a3 | ||
|
|
d32ccc817a | ||
|
|
19cc665f2d | ||
|
|
031e9105e2 | ||
|
|
35c5d64863 | ||
|
|
0b05756d12 | ||
|
|
3548dcbb67 | ||
|
|
b897b2a0e9 | ||
|
|
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 | ||
|
|
94f2bec329 |
@@ -1,3 +1,8 @@
|
||||
---
|
||||
name: v2rayNG程序问题
|
||||
about: 创建一个报告来帮助我们改进
|
||||
---
|
||||
|
||||
在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。
|
||||
|
||||
|
||||
@@ -14,7 +19,8 @@
|
||||
|
||||
### 日志信息
|
||||
<details>
|
||||
通过 `adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main` 获取日志。请自行删减日志中可能出现的敏感信息。
|
||||
|
||||
通过`adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main`获取日志。请自行删减日志中可能出现的敏感信息。
|
||||
|
||||
如果问题可重现,建议先执行`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的问题,请至这个链接讨论。
|
||||
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,11 +1,12 @@
|
||||
package CoreI
|
||||
|
||||
import (
|
||||
v2core "v2ray.com/core"
|
||||
v2core "github.com/xtls/xray-core/core"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
IsRunning bool
|
||||
IsTRunning bool
|
||||
PackageName string
|
||||
PackageCodePath string
|
||||
|
||||
@@ -13,7 +14,7 @@ type Status struct {
|
||||
}
|
||||
|
||||
func CheckVersion() int {
|
||||
return 21
|
||||
return 23
|
||||
}
|
||||
|
||||
func (v *Status) GetDataDir() 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/geoip.dat > geoip.dat
|
||||
|
||||
shippedBinary:
|
||||
cd shippedBinarys; $(MAKE) shippedBinary
|
||||
|
||||
fetchDep:
|
||||
-go get github.com/2dust/AndroidLibV2rayLite
|
||||
go get github.com/2dust/AndroidLibV2rayLite
|
||||
|
||||
ANDROID_HOME=$(HOME)/android-sdk-linux
|
||||
export ANDROID_HOME
|
||||
PATH:=$(PATH):$(GOPATH)/bin
|
||||
export PATH
|
||||
downloadGoMobile:
|
||||
go get golang.org/x/mobile/cmd/...
|
||||
sudo apt-get install -qq libstdc++6:i386 lib32z1 expect
|
||||
cd ~ ;curl -L https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/ubuntu-cli-install-android-sdk.sh | sudo bash - > /dev/null
|
||||
ls ~
|
||||
ls ~/android-sdk-linux/
|
||||
gomobile init ;gomobile bind -v -tags json github.com/2dust/AndroidLibV2rayLite
|
||||
|
||||
BuildMobile:
|
||||
@echo Stub
|
||||
gomobile init
|
||||
gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
|
||||
|
||||
all: asset pb shippedBinary fetchDep
|
||||
all: asset pb
|
||||
@echo DONE
|
||||
|
||||
@@ -3,14 +3,13 @@ package Escort
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"log"
|
||||
|
||||
"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)
|
||||
count := 0
|
||||
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)
|
||||
log.Println("EscortRun Waiting....")
|
||||
|
||||
if count > 0 {
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
sendFd()
|
||||
}()
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
log.Println("EscortRun cmd.Wait err:", err)
|
||||
}
|
||||
|
||||
@@ -1 +1,16 @@
|
||||
# AndroidLibV2rayLite
|
||||
|
||||
进入`AndroidLibV2rayLite`文件夹
|
||||
|
||||
```
|
||||
go mod download
|
||||
```
|
||||
|
||||
|
||||
|
||||
编译aar
|
||||
|
||||
```
|
||||
gomobile bind -v -o android.aar -target=android ./
|
||||
```
|
||||
|
||||
|
||||
@@ -12,8 +12,10 @@ import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
v2net "v2ray.com/core/common/net"
|
||||
v2internet "v2ray.com/core/transport/internet"
|
||||
v2net "github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
v2internet "github.com/xtls/xray-core/transport/internet"
|
||||
)
|
||||
|
||||
type protectSet interface {
|
||||
@@ -190,6 +192,11 @@ func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Init implement internet.SystemDialer
|
||||
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// Dial exported as the protected dial method
|
||||
func (d *ProtectedDialer) Dial(ctx context.Context,
|
||||
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
v2net "v2ray.com/core/common/net"
|
||||
v2net "github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
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/
|
||||
|
||||
pushd $TMPDIR
|
||||
git clone --depth=1 https://github.com/shadowsocks/badvpn.git
|
||||
git clone --depth=1 https://github.com/shadowsocks/libancillary.git
|
||||
git clone --depth=1 https://github.com/XTLS/badvpn.git
|
||||
git clone --depth=1 https://github.com/XTLS/libancillary.git
|
||||
$NDK_HOME/ndk-build \
|
||||
NDK_PROJECT_PATH=. \
|
||||
APP_BUILD_SCRIPT=./tun2socks.mk \
|
||||
@@ -34,14 +34,10 @@ $NDK_HOME/ndk-build \
|
||||
NDK_OUT=$TMPDIR/tmp \
|
||||
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
|
||||
|
||||
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/shippedBinarys/ArchDep/arm/
|
||||
install -v -m755 libs/arm64-v8a/tun2socks $__dir/shippedBinarys/ArchDep/arm64/
|
||||
install -v -m755 libs/x86/tun2socks $__dir/shippedBinarys/ArchDep/386/
|
||||
install -v -m755 libs/x86_64/tun2socks $__dir/shippedBinarys/ArchDep/amd64/
|
||||
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
|
||||
install -v -m755 libs/arm64-v8a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
|
||||
install -v -m755 libs/x86/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
|
||||
install -v -m755 libs/x86_64/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
|
||||
popd
|
||||
|
||||
pushd $__dir/shippedBinarys
|
||||
make clean && make shippedBinary
|
||||
popd
|
||||
|
||||
rm -rf $TMPDIR
|
||||
rm -rf $TMPDIR
|
||||
|
||||
49
AndroidLibV2rayLite/go.mod
Normal file
49
AndroidLibV2rayLite/go.mod
Normal file
@@ -0,0 +1,49 @@
|
||||
module github.com/2dust/AndroidLibV2rayLite
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/xtls/xray-core v1.5.7
|
||||
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cheekybits/genny v1.0.0 // indirect
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||
github.com/lucas-clemente/quic-go v0.27.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
||||
github.com/nxadm/tail v1.4.8 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pires/go-proxyproto v0.6.2 // indirect
|
||||
github.com/refraction-networking/utls v1.1.0 // indirect
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||
github.com/sagernet/sing v0.0.0-20220605012533-e0f722558141 // indirect
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220605012719-1e22882ea853 // indirect
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb // indirect
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e // indirect
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 // indirect
|
||||
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.11-0.20220325154526-54af36eca237 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 // indirect
|
||||
google.golang.org/grpc v1.47.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
lukechampine.com/blake3 v1.1.7 // indirect
|
||||
)
|
||||
408
AndroidLibV2rayLite/go.sum
Normal file
408
AndroidLibV2rayLite/go.sum
Normal file
@@ -0,0 +1,408 @@
|
||||
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
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/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
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-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0=
|
||||
github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140/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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
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 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
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/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
|
||||
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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
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.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
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.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
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-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
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.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/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/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12 h1:p9dKCg8i4gmOxtv35DvrYoWqYzQrvEVdjQ762Y0OqZE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucas-clemente/quic-go v0.27.1 h1:sOw+4kFSVrdWOYmUjufQ9GBVPqZ+tu+jMtXxXNmRJyk=
|
||||
github.com/lucas-clemente/quic-go v0.27.1/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
|
||||
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-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
|
||||
github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
|
||||
github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
|
||||
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.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||
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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
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/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
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/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
|
||||
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8=
|
||||
github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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 v1.1.0 h1:dKXJwSqni/t5csYJ+aQcEgqB7AMWYi6EUc9u3bEmjX0=
|
||||
github.com/refraction-networking/utls v1.1.0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sagernet/sing v0.0.0-20220605012533-e0f722558141 h1:YN9EeHRIFYKei1woz2OucLTSpiNrULAHH+jl6upDemQ=
|
||||
github.com/sagernet/sing v0.0.0-20220605012533-e0f722558141/go.mod h1:w2HnJzXKHpD6F5Z/9XlSD4qbcpHY2RSZuQnFzqgELMg=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220605012719-1e22882ea853 h1:t1pn8v3kPvlaST/fY7GsPV8yIUFJDXzNcmSYQ+nrbEM=
|
||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220605012719-1e22882ea853/go.mod h1:Afu8UIFlEwxTnlidTMSWqWHIduAWOrwY0tFr2LxBACQ=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb/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/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
|
||||
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
|
||||
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-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
|
||||
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU=
|
||||
github.com/xtls/xray-core v1.5.7 h1:xQqUpRtzVHL/to2sDmvgn9GelD0jIwC0I8B36hbDgDo=
|
||||
github.com/xtls/xray-core v1.5.7/go.mod h1:H4q5qoCwtsYeF06Zsb0tk8oV8UuaQ9NdnOaPv49Cj7I=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd h1:Uo/x0Ir5vQJ+683GXB9Ug+4fcjsbp7z7Ul8UaZbhsRM=
|
||||
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd/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-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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-20220518205345-8578da9835fd h1:x1GptNaTtxPAlTVIAJk61fuXg0y17h09DTxyb+VNC/k=
|
||||
golang.org/x/mobile v0.0.0-20220518205345-8578da9835fd/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
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-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93 h1:MYimHLfoXEpOhqd/zgoA/uoXzHB86AEky4LAx5ij9xA=
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
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-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68 h1:z8Hj/bl9cOV2grsOpEaQFUaly0JWN3i97mo3jXKJNp0=
|
||||
golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.11-0.20220325154526-54af36eca237 h1:mAhaIX1KEgotq+ju3XYdXUHvll7bzJDTgiDzIAKDdPc=
|
||||
golang.org/x/tools v0.1.11-0.20220325154526-54af36eca237/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618=
|
||||
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
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-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58 h1:a221mAAEAzq4Lz6ZWRkcS8ptb2mxoxYSt4N68aRyQHM=
|
||||
google.golang.org/genproto v0.0.0-20220527130721-00d5c0f3be58/go.mod h1:yKyY4AMRwFiC8yMMNaMi+RkCnjZJt9LoWuvhXjMs+To=
|
||||
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.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
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=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
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 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
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.3/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.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
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=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||
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/Process/Escort"
|
||||
"github.com/2dust/AndroidLibV2rayLite/VPN"
|
||||
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
|
||||
mobasset "golang.org/x/mobile/asset"
|
||||
|
||||
v2core "v2ray.com/core"
|
||||
v2filesystem "v2ray.com/core/common/platform/filesystem"
|
||||
v2stats "v2ray.com/core/features/stats"
|
||||
v2serial "v2ray.com/core/infra/conf/serial"
|
||||
_ "v2ray.com/core/main/distro/all"
|
||||
v2internet "v2ray.com/core/transport/internet"
|
||||
v2core "github.com/xtls/xray-core/core"
|
||||
v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
||||
v2stats "github.com/xtls/xray-core/features/stats"
|
||||
v2serial "github.com/xtls/xray-core/infra/conf/serial"
|
||||
_ "github.com/xtls/xray-core/main/distro/all"
|
||||
v2internet "github.com/xtls/xray-core/transport/internet"
|
||||
|
||||
v2applog "v2ray.com/core/app/log"
|
||||
v2commlog "v2ray.com/core/common/log"
|
||||
v2applog "github.com/xtls/xray-core/app/log"
|
||||
v2commlog "github.com/xtls/xray-core/common/log"
|
||||
)
|
||||
|
||||
const (
|
||||
v2Assert = "v2ray.location.asset"
|
||||
v2Assert = "xray.location.asset"
|
||||
assetperfix = "/dev/libv2rayfs0/asset"
|
||||
)
|
||||
|
||||
@@ -49,6 +48,7 @@ type V2RayPoint struct {
|
||||
ConfigureFileContent string
|
||||
EnableLocalDNS bool
|
||||
ForwardIpv6 bool
|
||||
ProxyOnly bool
|
||||
}
|
||||
|
||||
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
||||
@@ -58,7 +58,6 @@ type V2RayVPNServiceSupportsSet interface {
|
||||
Shutdown() int
|
||||
Protect(int) int
|
||||
OnEmitStatus(int, string) int
|
||||
SendFd() int
|
||||
}
|
||||
|
||||
/*RunLoop Run V2Ray main loop
|
||||
@@ -82,7 +81,6 @@ func (v *V2RayPoint) RunLoop() (err error) {
|
||||
if !v.dialer.IsVServerReady() {
|
||||
log.Println("vServer cannot resolved, shutdown")
|
||||
v.StopLoop()
|
||||
v.SupportSet.Shutdown()
|
||||
}
|
||||
|
||||
// stop waiting if manually closed
|
||||
@@ -101,9 +99,7 @@ func (v *V2RayPoint) StopLoop() (err error) {
|
||||
v.v2rayOP.Lock()
|
||||
defer v.v2rayOP.Unlock()
|
||||
if v.status.IsRunning {
|
||||
close(v.closeChan)
|
||||
v.shutdownInit()
|
||||
v.SupportSet.OnEmitStatus(0, "Closed")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -113,6 +109,10 @@ func (v *V2RayPoint) GetIsRunning() bool {
|
||||
return v.status.IsRunning
|
||||
}
|
||||
|
||||
func (v *V2RayPoint) GetIsTRunning() bool {
|
||||
return v.status.IsTRunning
|
||||
}
|
||||
|
||||
//Delegate Funcation
|
||||
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
||||
if v.statsManager == nil {
|
||||
@@ -126,24 +126,19 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
||||
}
|
||||
|
||||
func (v *V2RayPoint) shutdownInit() {
|
||||
v.status.IsRunning = false
|
||||
close(v.closeChan)
|
||||
v.statsManager = nil
|
||||
v.status.Vpoint.Close()
|
||||
v.status.Vpoint = nil
|
||||
v.statsManager = nil
|
||||
v.status.IsRunning = false
|
||||
|
||||
v.escorter.EscortingDown()
|
||||
|
||||
v.SupportSet.Shutdown()
|
||||
v.SupportSet.OnEmitStatus(0, "Closed")
|
||||
}
|
||||
|
||||
func (v *V2RayPoint) pointloop() error {
|
||||
if err := v.runTun2socks(); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
|
||||
v.EnableLocalDNS,
|
||||
v.ForwardIpv6,
|
||||
v.DomainName)
|
||||
|
||||
log.Println("loading v2ray config")
|
||||
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
||||
if err != nil {
|
||||
@@ -171,6 +166,21 @@ func (v *V2RayPoint) pointloop() error {
|
||||
v.SupportSet.Prepare()
|
||||
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
|
||||
v.SupportSet.OnEmitStatus(0, "Running")
|
||||
|
||||
v.status.IsTRunning = false
|
||||
if !v.ProxyOnly {
|
||||
if err := v.runTun2socks(); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
v.status.IsTRunning = true
|
||||
|
||||
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
|
||||
v.EnableLocalDNS,
|
||||
v.ForwardIpv6,
|
||||
v.DomainName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -227,17 +237,10 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
|
||||
}
|
||||
|
||||
func (v V2RayPoint) runTun2socks() error {
|
||||
shipb := shippedBinarys.FirstRun{Status: v.status}
|
||||
if err := shipb.CheckAndExport(); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
v.escorter.EscortingUp()
|
||||
go v.escorter.EscortRun(
|
||||
v.status.GetApp("libtun2socks.so"),
|
||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
|
||||
v.SupportSet.SendFd)
|
||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
|
||||
|
||||
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"
|
||||
"os"
|
||||
|
||||
v2commlog "v2ray.com/core/common/log"
|
||||
v2commlog "github.com/xtls/xray-core/common/log"
|
||||
)
|
||||
|
||||
type consoleLogWriter struct {
|
||||
|
||||
228
ExtRes/values-vi/strings.xml
Normal file
228
ExtRes/values-vi/strings.xml
Normal file
@@ -0,0 +1,228 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_widget_name">Kết nối ngay</string>
|
||||
<string name="app_tile_name">Kết nối ngay</string>
|
||||
<string name="app_tile_first_use">Vui lòng thêm một cấu hình vào v2rayNG để sử dụng.</string>
|
||||
<string name="navigation_drawer_open">Mở menu ứng dụng</string>
|
||||
<string name="navigation_drawer_close">Đóng menu ứng dụng</string>
|
||||
<string name="migration_success">Đã chuyển dữ liệu!</string>
|
||||
<string name="migration_fail">Không thể chuyển dữ liệu!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">Ngắt kết nối v2rayNG</string>
|
||||
<string name="toast_permission_denied">Vui lòng cấp quyền cần thiết cho v2rayNG. Bạn đã từ chối các quyền cần thiết như Camera hay Bộ nhớ.</string>
|
||||
<string name="notification_action_more">Nhấn để biết thêm</string>
|
||||
<string name="toast_services_start">Đang bắt đầu dịch vụ v2rayNG.</string>
|
||||
<string name="toast_services_stop">Đã dừng dịch vụ v2rayNG.</string>
|
||||
<string name="toast_services_success">Đã bắt đầu dịch vụ v2rayNG.</string>
|
||||
<string name="toast_services_failure">Không thể bắt đầu dịch vụ, hãy thử kiểm tra lại cấu hình hoặc khởi động lại thiết bị.</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">V2RayNG App :3</string>
|
||||
<string name="menu_item_add_config">Thêm cấu hình</string>
|
||||
<string name="menu_item_save_config">Lưu cấu hình</string>
|
||||
<string name="menu_item_del_config">Xoá cấu hình</string>
|
||||
<string name="menu_item_import_config_qrcode">Nhập cấu hình từ mã QR</string>
|
||||
<string name="menu_item_import_config_clipboard">Nhập cấu hình từ bộ nhớ tạm thời</string>
|
||||
<string name="menu_item_import_config_manually_vmess">Nhập thủ công [Vmess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">Nhập thủ công [VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">Nhập thủ công [Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">Nhập thủ công [Socks]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">Nhập thủ công [Trojan]</string>
|
||||
<string name="menu_item_import_config_custom">Nâng cao / Cấu hình tùy chỉnh</string>
|
||||
<string name="menu_item_import_config_custom_clipboard">Nhập cấu hình tùy chỉnh từ bộ nhớ tạm thời</string>
|
||||
<string name="menu_item_import_config_custom_local">Nhập cấu hình tùy chỉnh từ Tệp</string>
|
||||
<string name="menu_item_import_config_custom_url">Nhập cấu hình tùy chỉnh từ URL</string>
|
||||
<string name="menu_item_import_config_custom_url_scan">Nhập cấu hình tùy chỉnh quét URL</string>
|
||||
<string name="del_config_comfirm">Bạn có muốn xóa cấu hình ?</string>
|
||||
<string name="server_lab_remarks">Tên cấu hình</string>
|
||||
<string name="server_lab_address">Địa chỉ</string>
|
||||
<string name="server_lab_port">Cổng</string>
|
||||
<string name="server_lab_id">Địa chỉ ID</string>
|
||||
<string name="server_lab_alterid">alterId</string>
|
||||
<string name="server_lab_security">Bảo mật</string>
|
||||
<string name="server_lab_network">Kiểu kết nối</string>
|
||||
<string name="server_lab_more_function">Nâng cao</string>
|
||||
<string name="server_lab_head_type">Kiểu Head</string>
|
||||
<string name="server_lab_mode_type">Chế độ gRPC</string>
|
||||
<string name="server_lab_request_host">Yêu cầu host(host/ws host/h2 host)/Bảo mật QUIC</string>
|
||||
<string name="server_lab_path">Đường dẫn (ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">tls</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_sni">Địa chỉ SNI</string>
|
||||
<string name="server_lab_address3">Địa chỉ</string>
|
||||
<string name="server_lab_port3">Cổng</string>
|
||||
<string name="server_lab_id3">Mật khẩu</string>
|
||||
<string name="server_lab_security3">Bảo mật</string>
|
||||
<string name="server_lab_id4">Mật khẩu(Bổ sung)</string>
|
||||
<string name="server_lab_security4">Tên người dùng(Bổ sung)</string>
|
||||
<string name="server_lab_encryption">Mã hoá</string>
|
||||
<string name="server_lab_flow">flow</string>
|
||||
<string name="toast_success">Đã thực hiện thành công thao tác của bạn, Nếu có gì đó không ổn, hãy thao tác lại.</string>
|
||||
<string name="toast_failure">Đã xảy ra lỗi, hãy thử kiểm tra lại hoặc thử lại.</string>
|
||||
<string name="toast_none_data">Không có gì ở đây</string>
|
||||
<string name="toast_incorrect_protocol">Không đúng protocol</string>
|
||||
<string name="toast_decoding_failed">Không thể decode</string>
|
||||
<string name="title_file_chooser">Vui lòng chọn tệp cấu hình</string>
|
||||
<string name="toast_require_file_manager">Vui lòng cài đặt trình quản lý tệp để tiếp tục.</string>
|
||||
<string name="server_customize_config">Cấu hình tùy chỉnh</string>
|
||||
<string name="toast_config_file_invalid">Cấu hình không hợp lệ</string>
|
||||
<string name="server_lab_content">Nội dung</string>
|
||||
<string name="toast_none_data_clipboard">Không có dữ liệu nào trong bộ nhớ tạm thời</string>
|
||||
<string name="toast_invalid_url">URL không hợp lệ hoặc không có gì</string>
|
||||
<string name="server_lab_need_inbound">Vui lòng đảm bảo cấu hình tùy chỉnh này không bị lỗi trước khi sử dụng. v2rayNG được Dịch Tiếng Việt bởi CuynuTT😘</string>
|
||||
<string name="toast_malformed_josn">Cấu hình không hợp lệ </string>
|
||||
<string name="server_lab_request_host6">Host(SNI)(Bổ sung)</string>
|
||||
<string name="toast_asset_copy_failed">Không thể sao chép tệp tin, hãy dùng trình quản lý tệp</string>
|
||||
<string name="menu_item_add_file">Thêm tệp</string>
|
||||
<string name="menu_item_download_file">Tải xuống tệp tin</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">Đang tải...</string>
|
||||
<string name="menu_item_search">Tìm kiếm</string>
|
||||
<string name="menu_item_select_all">Chọn tất cả</string>
|
||||
<string name="msg_enter_keywords">Nhập từ khoá</string>
|
||||
<string name="switch_bypass_apps_mode">Bỏ qua kết nối VPN</string>
|
||||
<string name="menu_item_select_proxy_app">Tự động chọn ứng dụng Proxy</string>
|
||||
<string name="msg_downloading_content">Đang tải xuống nội dung...</string>
|
||||
<string name="menu_item_export_proxy_app">Xuất và sao chép</string>
|
||||
<string name="menu_item_import_proxy_app">Nhập từ bộ nhớ tạm thời</string>
|
||||
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">Cài đặt</string>
|
||||
<string name="title_advanced">Cài đặt nâng cao</string>
|
||||
<string name="title_vpn_settings">Cài đặt cho VPN</string>
|
||||
<string name="title_pref_per_app_proxy">Proxy cho ứng dụng</string>
|
||||
<string name="summary_pref_per_app_proxy">Chung: Ứng dụng đã chọn sẽ kết nối Proxy, Chưa lựa chọn sẽ kết nối trực tiếp; \nBỏ qua kết nối: Ứng dụng được chọn sẽ trực tiếp kết nối, không lựa chọn Proxy. \nLựa chọn để tự động chọn ứng dụng Proxy trong Menu.</string>
|
||||
|
||||
<string name="title_pref_mux_enabled">Cho phép Mux</string>
|
||||
<string name="summary_pref_mux_enabled">Bật lên có thể làm tăng tốc độ mạng và chuyển mạng nhanh hơn.</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">Cho phép hiển thị tốc độ mạng</string>
|
||||
<string name="summary_pref_speed_enabled">Hiển thị tốc độ mạng hiện tại trên thanh thông báo.\nBiểu tượng trên thanh trạng thái có thể thay đổi tùy vào mức sử dụng.</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">Cho phép Sniffing</string>
|
||||
<string name="summary_pref_sniffing_enabled">Thử chuyển kết nối hiện tại của bạn qua trung gian để trung gian xử lý kết nối về lại cho bạn (Mặc định là bật, hãy tắt nó nếu kết nối không ổn định.)</string>
|
||||
|
||||
<string name="title_pref_local_dns_enabled">Cho phép DNS cục bộ</string>
|
||||
<string name="summary_pref_local_dns_enabled">DNS được xử lý bởi mô đun của lõi DNS.
|
||||
(Khuyến cáo, nếu cần lộ trình Bẻ khoá LAN và
|
||||
địa chỉ mainland)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">Cho phép DNS giả</string>
|
||||
<string name="summary_pref_fake_dns_enabled">DNS cục bộ trả về địa chỉ IP giả (Nhanh hơn, nhưng có thể không hoạt động với một số ứng dụng)</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">Ưu tiên IPv6</string>
|
||||
<string name="summary_pref_prefer_ipv6">Ưu tiên sử dụng địa chỉ IPv6 cho kết nối và lộ trình.</string>
|
||||
|
||||
<string name="title_pref_routing">Lộ trình</string>
|
||||
<string name="title_pref_routing_domain_strategy">Tùy chọn tên miền</string>
|
||||
<string name="title_pref_routing_mode">Tùy chỉnh quy tắc lộ trình</string>
|
||||
<string name="title_pref_routing_custom">Tùy chỉnh lộ trình</string>
|
||||
|
||||
<string name="title_pref_remote_dns">Điều khiển DNS (Bổ sung)</string>
|
||||
<string name="summary_pref_remote_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_vpn_dns">VPN DNS (Chỉ IPv4/v6)</string>
|
||||
|
||||
<string name="title_pref_domestic_dns">Domestic DNS (Bổ sung)</string>
|
||||
<string name="summary_pref_domestic_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">Cho phép kết nối từ mạng LAN</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">Các thiết bị khác có thể kết nối đến proxy bởi địa chỉ IP thông qua socks/http, Chỉ bật khi bạn tin tưởng kết nối để tránh kết nối lạ.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Cho phép kết nối từ mạng LAN, Đảm bảo rằng bạn tin tưởng kết nối hiện tại.</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">Cho phép đặt lại allowInsecure</string>
|
||||
<string name="summary_pref_allow_insecure">Khi kết nối TLS, đặt cài đặt allowInsecure thành mặc định</string>
|
||||
|
||||
<string name="title_pref_socks_port">Cổng Proxy SOCKS5</string>
|
||||
<string name="summary_pref_socks_port">Cổng Proxy SOCKS5</string>
|
||||
|
||||
<string name="title_pref_http_port">Cổng Proxy HTTP</string>
|
||||
<string name="summary_pref_http_port">Cổng Proxy HTTP</string>
|
||||
|
||||
<string name="title_pref_local_dns_port">Cổng DNS cục bộ</string>
|
||||
<string name="summary_pref_local_dns_port">Cổng DNS cục bộ</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">Hiển thị thông báo xác nhận xoá cấu hình</string>
|
||||
<string name="summary_pref_confirm_remove">Hiển thị thông báo xác nhận xoá cấu hình khi bạn xoá một cấu hình.</string>
|
||||
|
||||
<string name="title_pref_feedback">Phản hồi lỗi</string>
|
||||
<string name="summary_pref_feedback">Phản hồi cải tiến hoặc bug lên GitHub</string>
|
||||
<string name="summary_pref_tg_group">Tham gia nhóm Telegram</string>
|
||||
<string name="toast_tg_app_not_found">Không tìm thấy ứng dụng Telegram</string>
|
||||
|
||||
<string name="title_pref_promotion">Quảng cáo Server</string>
|
||||
<string name="summary_pref_promotion">Quảng cáo,nhấn để biết thêm(Ủng hộ có thể được gỡ bỏ)</string>
|
||||
|
||||
<string name="title_core_loglevel">Mức độ nhật ký</string>
|
||||
<string name="title_mode">Chế độ kết nối</string>
|
||||
<string name="title_mode_help">Nhấn vào đây nếu bạn cần trợ giúp</string>
|
||||
<string name="title_language">Ngôn ngữ ứng dụng</string>
|
||||
|
||||
<string name="title_logcat">Nhật ký hoạt động</string>
|
||||
<string name="logcat_copy">Sao chép nhật ký</string>
|
||||
<string name="logcat_clear">Xoá nhật ký</string>
|
||||
<string name="title_service_restart">Kết nối lại v2rayNG</string>
|
||||
<string name="title_del_all_config">Xoá tất cả cấu hình</string>
|
||||
<string name="title_del_invalid_config">Xoá cấu hình lỗi (Kiểm tra trước)</string>
|
||||
<string name="title_export_all">Xuất và sao chép tất cả cấu hình</string>
|
||||
<string name="title_sub_setting">Các gói đăng ký</string>
|
||||
<string name="sub_setting_remarks">Tên các gói đăng ký</string>
|
||||
<string name="sub_setting_url">URL gói đăng ký</string>
|
||||
<string name="sub_setting_enable">Sử dụng gói đăng ký này</string>
|
||||
<string name="title_sub_update">Cập nhật các gói đăng ký</string>
|
||||
<string name="title_ping_all_server">Ping tất cả máy chủ</string>
|
||||
<string name="title_real_ping_all_server">Kiểm tra máy chủ</string>
|
||||
<string name="title_user_asset_setting">Tệp Geo assets</string>
|
||||
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
|
||||
<string name="title_filter_config">Lọc cấu hình theo các gói đăng ký</string>
|
||||
<string name="filter_config_all">Hiển thị tất cả các gói đăng ký</string>
|
||||
|
||||
<string name="tasker_start_service">Bắt đầu dịch vụ</string>
|
||||
<string name="tasker_setting_confirm">Xác nhận</string>
|
||||
|
||||
<string name="routing_settings_title">Cài đặt lộ trình</string>
|
||||
<string name="routing_settings_tips">Được phân cách bằng dấu chấm phẩy(,),Hãy nhớ nó để lưu lại.</string>
|
||||
<string name="routing_settings_save">Lưu lại</string>
|
||||
<string name="routing_settings_delete">Xoá</string>
|
||||
<string name="routing_settings_scan_replace">Dò và thay thế</string>
|
||||
<string name="routing_settings_scan_append">Dò và nối</string>
|
||||
<string name="routing_settings_default_rules">Đặt luật lệ lộ trình mặc định</string>
|
||||
|
||||
<string name="connection_test_pending">Kiểm tra kết nối</string>
|
||||
<string name="connection_test_testing">Đang kiểm tra kết nối mạng…</string>
|
||||
<string name="connection_test_available">Đã kiểm tra kết nối mạng thành công, Ping hiện tại là %d</string>
|
||||
<string name="connection_test_error">Lỗi kết nối mạng hãy thử đổi cấu hình hoặc kiểm tra lại. Mã lỗi: %s</string>
|
||||
<string name="connection_test_fail">Không có kết nối mạng</string>
|
||||
<string name="connection_test_error_status_code">Mã lỗi: #%d</string>
|
||||
<string name="connection_connected">Đã kết nối, hãy nhấn vào đây để kiểm tra kết nối mạng.</string>
|
||||
<string name="connection_not_connected">Chưa kết nối, hãy thêm một cấu hình để kết nối. Đừng để bị lừa đảo bởi cấu hình mất tiền,Dịch TV bởi CuynuTT😘</string>
|
||||
|
||||
<string-array name="share_method">
|
||||
<item>Xuất ra mã QR (Chụp màn hình để lưu)</item>
|
||||
<item>Sao chép cấu hình này</item>
|
||||
<item>Sao chép thành cấu hình tùy chỉnh</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>proxy URL hoặc IP</item>
|
||||
<item>direct URL hoặc IP</item>
|
||||
<item>URL đã chặn hoặc IP</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_mode">
|
||||
<item>Proxy Global</item>
|
||||
<item>Bẻ khoá địa chỉ LAN rồi proxy</item>
|
||||
<item>Bẻ khoá địa chỉ mainland rồi proxy</item>
|
||||
<item>Bẻ khoá LAN và địa chỉ mainland rồi proxy</item>
|
||||
<item>Trực tiếp Global</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>Chế độ VPN</item>
|
||||
<item>Chế độ Proxy</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
226
ExtRes/values-zh-rCN/strings.xml
Normal file
226
ExtRes/values-zh-rCN/strings.xml
Normal file
@@ -0,0 +1,226 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_widget_name">开关</string>
|
||||
<string name="app_tile_name">开关</string>
|
||||
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
<string name="migration_success">数据迁移成功!</string>
|
||||
<string name="migration_fail">数据迁移失败啦!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">停止</string>
|
||||
<string name="toast_permission_denied">无法取得权D:\vssHotel\SourceCode\Hotel.root\Hotel\Clubank.Hotel\FrontCounter\CheckoutListForm.cs限</string>
|
||||
<string name="notification_action_more">点击了解更多</string>
|
||||
<string name="toast_services_start">启动服务中</string>
|
||||
<string name="toast_services_stop">关闭中</string>
|
||||
<string name="toast_services_success">启动服务成功</string>
|
||||
<string name="toast_services_failure">启动服务失败</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">配置文件</string>
|
||||
<string name="menu_item_add_config">添加配置</string>
|
||||
<string name="menu_item_save_config">保存配置</string>
|
||||
<string name="menu_item_del_config">删除配置</string>
|
||||
<string name="menu_item_import_config_qrcode">扫描二维码</string>
|
||||
<string name="menu_item_import_config_clipboard">从剪贴板导入</string>
|
||||
<string name="menu_item_import_config_manually_vmess">手动输入[Vmess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">手动输入[VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">手动输入[Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">手动输入[Socks]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">手动输入[Trojan]</string>
|
||||
<string name="menu_item_import_config_custom">自定义配置</string>
|
||||
<string name="menu_item_import_config_custom_clipboard">从剪贴板导入自定义配置</string>
|
||||
<string name="menu_item_import_config_custom_local">从本地导入自定义配置</string>
|
||||
<string name="menu_item_import_config_custom_url">剪贴板URL导入自定义配置</string>
|
||||
<string name="menu_item_import_config_custom_url_scan">扫描URL导入自定义配置</string>
|
||||
<string name="del_config_comfirm">确认删除?</string>
|
||||
<string name="server_lab_remarks">别名(remarks)</string>
|
||||
<string name="server_lab_address">地址(address)</string>
|
||||
<string name="server_lab_port">端口(port)</string>
|
||||
<string name="server_lab_id">用户ID(id)</string>
|
||||
<string name="server_lab_alterid">额外ID(alterId)</string>
|
||||
<string name="server_lab_security">加密方式(security)</string>
|
||||
<string name="server_lab_network">传输协议(network)</string>
|
||||
<string name="server_lab_more_function">底层传输方式(transport)</string>
|
||||
<string name="server_lab_head_type">伪装类型(type)</string>
|
||||
<string name="server_lab_mode_type">gRPC 传输模式 (mode)</string>
|
||||
<string name="server_lab_request_host">伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">传输层安全(tls)</string>
|
||||
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">服务器地址</string>
|
||||
<string name="server_lab_port3">服务器端口</string>
|
||||
<string name="server_lab_id3">密码</string>
|
||||
<string name="server_lab_security3">加密方式</string>
|
||||
<string name="server_lab_id4">密码(可选)</string>
|
||||
<string name="server_lab_security4">用户名(可选)</string>
|
||||
<string name="server_lab_encryption">加密(encryption)</string>
|
||||
<string name="server_lab_flow">流控(flow)</string>
|
||||
<string name="toast_success">成功</string>
|
||||
<string name="toast_failure">失败</string>
|
||||
<string name="toast_none_data">没有数据</string>
|
||||
<string name="toast_incorrect_protocol">不正确的协议</string>
|
||||
<string name="toast_decoding_failed">解码失败</string>
|
||||
<string name="title_file_chooser">选择一个配置文件</string>
|
||||
<string name="toast_require_file_manager">请安装一个文件管理器</string>
|
||||
<string name="server_customize_config">自定义配置</string>
|
||||
<string name="toast_config_file_invalid">无效的配置文件</string>
|
||||
<string name="server_lab_content">内容</string>
|
||||
<string name="toast_none_data_clipboard">剪贴板中没有数据</string>
|
||||
<string name="toast_invalid_url">无效的网址</string>
|
||||
<string name="server_lab_need_inbound">确保inbounds port和设置中的一致</string>
|
||||
<string name="toast_malformed_josn">配置格式错误</string>
|
||||
<string name="server_lab_request_host6">Host(SNI)(可选)</string>
|
||||
<string name="toast_asset_copy_failed">失败, 请使用文件管理器</string>
|
||||
<string name="menu_item_add_file">添加文件</string>
|
||||
<string name="menu_item_download_file">下载文件</string>
|
||||
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">正在加载</string>
|
||||
<string name="menu_item_search">搜索</string>
|
||||
<string name="menu_item_select_all">全选</string>
|
||||
<string name="msg_enter_keywords">输入关键字</string>
|
||||
<string name="switch_bypass_apps_mode">绕行模式</string>
|
||||
<string name="menu_item_select_proxy_app">自动选中需代理应用</string>
|
||||
<string name="msg_downloading_content">正在下载内容</string>
|
||||
<string name="menu_item_export_proxy_app">导出至剪贴板</string>
|
||||
<string name="menu_item_import_proxy_app">从剪贴板导入</string>
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">设置</string>
|
||||
<string name="title_advanced">进阶设置</string>
|
||||
<string name="title_vpn_settings">VPN 设置</string>
|
||||
<string name="title_pref_per_app_proxy">分应用代理</string>
|
||||
<string name="summary_pref_per_app_proxy">常规:勾选的App被代理,未勾选的直连;\n绕行模式:勾选的App直连,未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
|
||||
|
||||
<string name="title_pref_mux_enabled">启用Mux多路复用</string>
|
||||
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">启用速度显示</string>
|
||||
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">启用流量探测</string>
|
||||
<string name="summary_pref_sniffing_enabled">从流量中探测域名 (默认启用)</string>
|
||||
|
||||
<string name="title_pref_local_dns_enabled">启用本地DNS</string>
|
||||
<string name="summary_pref_local_dns_enabled">DNS 请求导入 core 由 DNS 模块处理(推荐启用 如果需要路由绕过局域网及大陆地址)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">启用虚拟DNS</string>
|
||||
<string name="summary_pref_fake_dns_enabled">本地返回虚构解析结果 (减低延时 但个别应用可能无法使用)</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">IPv6优先</string>
|
||||
<string name="summary_pref_prefer_ipv6">App优先使用IPv6地址连接服务器,同时开启VPN的IPv6路由</string>
|
||||
|
||||
<string name="title_pref_routing">路由设置</string>
|
||||
<string name="title_pref_routing_domain_strategy">域名策略</string>
|
||||
<string name="title_pref_routing_mode">预定义规则</string>
|
||||
<string name="title_pref_routing_custom">自定义规则</string>
|
||||
|
||||
<string name="title_pref_remote_dns">远程DNS (可选)</string>
|
||||
<string name="summary_pref_remote_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
|
||||
|
||||
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
|
||||
<string name="summary_pref_domestic_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">允许来自局域网的连接</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">其他设备可以使用socks/http协议通过您的IP地址连接到代理,仅在受信任的网络中启用以避免未经授权的连接</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">允许来自局域网的连接,请确保处于受信网络</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">跳过证书验证(allowInsecure)</string>
|
||||
<string name="summary_pref_allow_insecure">传输层安全选tls时,默认跳过证书验证(allowInsecure)</string>
|
||||
|
||||
<string name="title_pref_socks_port">SOCKS5代理端口</string>
|
||||
<string name="summary_pref_socks_port">SOCKS5代理端口</string>
|
||||
|
||||
<string name="title_pref_http_port">HTTP代理端口</string>
|
||||
<string name="summary_pref_http_port">HTTP代理端口</string>
|
||||
|
||||
<string name="title_pref_local_dns_port">本地DNS端口</string>
|
||||
<string name="summary_pref_local_dns_port">本地DNS端口</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">删除配置文件确认</string>
|
||||
<string name="summary_pref_confirm_remove">删除配置文件是否需要用户二次确认</string>
|
||||
|
||||
<string name="title_pref_feedback">反馈</string>
|
||||
<string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
|
||||
<string name="summary_pref_tg_group">加入Telegram Group</string>
|
||||
<string name="toast_tg_app_not_found">未找到Telegram app</string>
|
||||
|
||||
<string name="title_pref_promotion">推广</string>
|
||||
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
|
||||
|
||||
<string name="title_core_loglevel">日志级别</string>
|
||||
<string name="title_mode">模式</string>
|
||||
<string name="title_mode_help">点此查看更多帮助</string>
|
||||
<string name="title_language">语言</string>
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">复制</string>
|
||||
<string name="logcat_clear">清除</string>
|
||||
<string name="title_service_restart">服务重启</string>
|
||||
<string name="title_del_all_config">删除全部配置</string>
|
||||
<string name="title_del_invalid_config">删除无效配置(先测试)</string>
|
||||
<string name="title_export_all">导出全部(非自定义)配置至剪贴板</string>
|
||||
<string name="title_sub_setting">订阅分组设置</string>
|
||||
<string name="sub_setting_remarks">备注</string>
|
||||
<string name="sub_setting_url">可选地址(url)</string>
|
||||
<string name="sub_setting_enable">启用更新</string>
|
||||
<string name="title_sub_update">更新订阅</string>
|
||||
<string name="title_ping_all_server">测试全部配置Tcping</string>
|
||||
<string name="title_real_ping_all_server">测试全部配置真连接</string>
|
||||
<string name="title_user_asset_setting">Geo 资源文件</string>
|
||||
<string name="title_sort_by_test_results">按测试结果排序</string>
|
||||
<string name="title_filter_config">过滤配置文件</string>
|
||||
<string name="filter_config_all">所有订阅分组</string>
|
||||
|
||||
<string name="tasker_start_service">启动服务</string>
|
||||
<string name="tasker_setting_confirm">确定</string>
|
||||
|
||||
<string name="routing_settings_title">路由设置</string>
|
||||
<string name="routing_settings_tips">用逗号(,)隔开,可以一行多个,记得保存</string>
|
||||
<string name="routing_settings_save">保存</string>
|
||||
<string name="routing_settings_delete">清空</string>
|
||||
<string name="routing_settings_scan_replace">扫描并替换</string>
|
||||
<string name="routing_settings_scan_append">扫描并追加</string>
|
||||
<string name="routing_settings_default_rules">设置默认路由规则</string>
|
||||
|
||||
<string name="connection_test_pending">"检查网络连接"</string>
|
||||
<string name="connection_test_testing">"测试中…"</string>
|
||||
<string name="connection_test_available">"连接成功:延时 %d 毫秒"</string>
|
||||
<string name="connection_test_error">"失败:%s"</string>
|
||||
<string name="connection_test_fail">"无互联网连接"</string>
|
||||
<string name="connection_test_error_status_code">"状态码无效(#%d)"</string>
|
||||
<string name="connection_connected">"已连接,点击测试连接"</string>
|
||||
<string name="connection_not_connected">"未连接"</string>
|
||||
|
||||
<string-array name="share_method">
|
||||
<item>二维码</item>
|
||||
<item>导出至剪贴板</item>
|
||||
<item>导出完整配置至剪贴板</item>
|
||||
</string-array>
|
||||
share_method
|
||||
<string-array name="routing_tag">
|
||||
<item>代理的网址或IP</item>
|
||||
<item>直连的网址或IP</item>
|
||||
<item>阻止的网址或IP</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_mode">
|
||||
<item>全局代理</item>
|
||||
<item>绕过局域网地址而后代理</item>
|
||||
<item>绕过大陆地址而后代理</item>
|
||||
<item>绕过局域网及大陆地址而后代理</item>
|
||||
<item>全局直连</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>仅代理</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
226
ExtRes/values-zh-rTW/strings.xml
Normal file
226
ExtRes/values-zh-rTW/strings.xml
Normal file
@@ -0,0 +1,226 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_widget_name">開關</string>
|
||||
<string name="app_tile_name">開關</string>
|
||||
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增伺服器</string>
|
||||
<string name="navigation_drawer_open">開啟導覽匣</string>
|
||||
<string name="navigation_drawer_close">關閉導覽匣</string>
|
||||
<string name="migration_success">資料遷移成功!</string>
|
||||
<string name="migration_fail">資料遷移失敗!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">停止</string>
|
||||
<string name="toast_permission_denied">無法取得此權限</string>
|
||||
<string name="notification_action_more">瞭解更多</string>
|
||||
<string name="toast_services_start">啟動服務</string>
|
||||
<string name="toast_services_stop">停止服務</string>
|
||||
<string name="toast_services_success">啟動服務成功</string>
|
||||
<string name="toast_services_failure">啟動服務失敗</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">組態檔案</string>
|
||||
<string name="menu_item_add_config">新增組態</string>
|
||||
<string name="menu_item_save_config">儲存組態</string>
|
||||
<string name="menu_item_del_config">刪除組態</string>
|
||||
<string name="menu_item_import_config_qrcode">從 QR Code 匯入組態</string>
|
||||
<string name="menu_item_import_config_clipboard">從剪貼簿匯入組態</string>
|
||||
<string name="menu_item_import_config_manually_vmess">手動鍵入 [Vmess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">手動鍵入 [VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">手動鍵入 [Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">手動鍵入 [Socks]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">手動鍵入 [Trojan]</string>
|
||||
<string name="menu_item_import_config_custom">自訂組態</string>
|
||||
<string name="menu_item_import_config_custom_clipboard">從剪貼簿匯入自訂組態</string>
|
||||
<string name="menu_item_import_config_custom_local">從 URL 匯入自訂組態</string>
|
||||
<string name="menu_item_import_config_custom_url">從 URL 匯入自訂組態</string>
|
||||
<string name="menu_item_import_config_custom_url_scan">掃描 URL 匯入自訂組態</string>
|
||||
<string name="del_config_comfirm">確定刪除?</string>
|
||||
<string name="server_lab_remarks">備註</string>
|
||||
<string name="server_lab_address">位址</string>
|
||||
<string name="server_lab_port">埠</string>
|
||||
<string name="server_lab_id">使用者 ID</string>
|
||||
<string name="server_lab_alterid">alterId</string>
|
||||
<string name="server_lab_security">安全性</string>
|
||||
<string name="server_lab_network">網路</string>
|
||||
<string name="server_lab_more_function">底層傳輸方式 (transport)</string>
|
||||
<string name="server_lab_head_type">標頭類型</string>
|
||||
<string name="server_lab_mode_type">gRPC 傳輸模式 (mode)</string>
|
||||
<string name="server_lab_request_host">要求主機 (host)(host/ws host/h2 host)/QUIC 加密方式</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密金鑰/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">傳輸層安全 (tls)</string>
|
||||
<string name="server_lab_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">伺服器位址</string>
|
||||
<string name="server_lab_port3">伺服器埠</string>
|
||||
<string name="server_lab_id3">密碼</string>
|
||||
<string name="server_lab_security3">加密方式</string>
|
||||
<string name="server_lab_id4">密碼 (可選)</string>
|
||||
<string name="server_lab_security4">使用者名稱 (可選)</string>
|
||||
<string name="server_lab_encryption">加密 (encryption)</string>
|
||||
<string name="server_lab_flow">流程 (flow)</string>
|
||||
<string name="toast_success">成功</string>
|
||||
<string name="toast_failure">失敗</string>
|
||||
<string name="toast_none_data">無資料</string>
|
||||
<string name="toast_incorrect_protocol">通訊協定不正確</string>
|
||||
<string name="toast_decoding_failed">解碼失敗</string>
|
||||
<string name="title_file_chooser">選取一個組態檔</string>
|
||||
<string name="toast_require_file_manager">請安裝檔案總管。</string>
|
||||
<string name="server_customize_config">自訂組態</string>
|
||||
<string name="toast_config_file_invalid">無效組態</string>
|
||||
<string name="server_lab_content">內容</string>
|
||||
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
|
||||
<string name="toast_invalid_url">URL 無效</string>
|
||||
<string name="server_lab_need_inbound">確保 inbounds port 和設定中的一致</string>
|
||||
<string name="toast_malformed_josn">組態格式不正確</string>
|
||||
<string name="server_lab_request_host6">Host(SNI)(可選)</string>
|
||||
<string name="toast_asset_copy_failed">失敗,請使用檔案總管</string>
|
||||
<string name="menu_item_add_file">新增檔案</string>
|
||||
<string name="menu_item_download_file">下載檔案</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">載入</string>
|
||||
<string name="menu_item_search">搜尋</string>
|
||||
<string name="menu_item_select_all">全選</string>
|
||||
<string name="msg_enter_keywords">輸入關鍵字</string>
|
||||
<string name="switch_bypass_apps_mode">略過模式</string>
|
||||
<string name="menu_item_select_proxy_app">自動選中需 Proxy 應用</string>
|
||||
<string name="msg_downloading_content">正在下載內容</string>
|
||||
<string name="menu_item_export_proxy_app">匯出至剪貼簿</string>
|
||||
<string name="menu_item_import_proxy_app">從剪貼簿匯入</string>
|
||||
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">設定</string>
|
||||
<string name="title_advanced">進階</string>
|
||||
<string name="title_vpn_settings">VPN 設定</string>
|
||||
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
|
||||
<string name="summary_pref_per_app_proxy">常規:勾選的 App 啟用 Proxy,未勾選的直接連線;\n繞行模式:勾選的 App 直接連線,未勾選的啟用 Proxy。\n可在選單中選擇自動選中需 Proxy 應用</string>
|
||||
|
||||
<string name="title_pref_mux_enabled">啟用 Mux</string>
|
||||
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">啟用速度顯示</string>
|
||||
<string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖示顯示流量的轉送狀況</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">啟用流量監聽</string>
|
||||
<string name="summary_pref_sniffing_enabled">從流量中監聽網域 (預設啟用)</string>
|
||||
|
||||
<string name="title_pref_local_dns_enabled">啟用本機 DNS</string>
|
||||
<string name="summary_pref_local_dns_enabled">DNS 請求匯入 core 由 DNS 模塊處理 (建議啟用,如果需要轉送略過區域網路及中國大陸)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">啟用假 DNS</string>
|
||||
<string name="summary_pref_fake_dns_enabled">本機退回假解析結果 (減低延時,但個別應用可能無法使用)</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">IPv6 偏好</string>
|
||||
<string name="summary_pref_prefer_ipv6">App 優先使用 IPv6 位址連線伺服器,同时開啟 VPN 的 IPv6 路由</string>
|
||||
|
||||
<string name="title_pref_routing">轉送設定</string>
|
||||
<string name="title_pref_routing_domain_strategy">網域策略</string>
|
||||
<string name="title_pref_routing_mode">轉送模式</string>
|
||||
<string name="title_pref_routing_custom">自訂轉送</string>
|
||||
|
||||
<string name="title_pref_remote_dns">遠端 DNS (可選)</string>
|
||||
<string name="summary_pref_remote_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_vpn_dns">VPN DNS (僅支援 IPv4/v6)</string>
|
||||
|
||||
<string name="title_pref_domestic_dns">國內 DNS (可選)</string>
|
||||
<string name="summary_pref_domestic_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">允許來自區域網路的連線</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">其他裝置可以使用 socks/http 協定透過您的 IP 位址連線到 Proxy,僅在受信任的網路中啟用以避免未經授權的連線</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">允許來自區域網路的連線,請確保處於受信網路</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">跳過憑證驗證 (allowInsecure)</string>
|
||||
<string name="summary_pref_allow_insecure">傳輸層安全選 tls 時,預設跳過憑證驗證 (allowInsecure)</string>
|
||||
|
||||
<string name="title_pref_socks_port">SOCKS5 Proxy 埠</string>
|
||||
<string name="summary_pref_socks_port">SOCKS5 Proxy 埠</string>
|
||||
|
||||
<string name="title_pref_http_port">HTTP Proxy 埠</string>
|
||||
<string name="summary_pref_http_port">HTTP Proxy 埠</string>
|
||||
|
||||
<string name="title_pref_local_dns_port">本機 DNS 埠</string>
|
||||
<string name="summary_pref_local_dns_port">本機 DNS 埠</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">刪除配置文件確認</string>
|
||||
<string name="summary_pref_confirm_remove">刪除配置文件是否需要用戶二次確認</string>
|
||||
|
||||
<string name="title_pref_feedback">意見回饋</string>
|
||||
<string name="summary_pref_feedback">前往 GitHub 回報錯誤</string>
|
||||
<string name="summary_pref_tg_group">加入 Telegram 群組</string>
|
||||
<string name="toast_tg_app_not_found">未找到 Telegram 應用程式</string>
|
||||
|
||||
<string name="title_pref_promotion">推廣</string>
|
||||
<string name="summary_pref_promotion">一些推廣,輕觸以檢視 (捐贈可去除)</string>
|
||||
|
||||
<string name="title_core_loglevel">記錄層級</string>
|
||||
<string name="title_mode">模式</string>
|
||||
<string name="title_mode_help">輕觸以檢視說明</string>
|
||||
<string name="title_language">語言</string>
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">複製</string>
|
||||
<string name="logcat_clear">清除</string>
|
||||
<string name="title_service_restart">服務重啟</string>
|
||||
<string name="title_del_all_config">刪除全部組態</string>
|
||||
<string name="title_del_invalid_config">刪除無效組態 (先偵測)</string>
|
||||
<string name="title_export_all">匯出全部 (非自訂) 組態至剪貼簿</string>
|
||||
<string name="title_sub_setting">訂閱分組設定</string>
|
||||
<string name="sub_setting_remarks">備註</string>
|
||||
<string name="sub_setting_url">Optional URL</string>
|
||||
<string name="sub_setting_enable">啟用更新</string>
|
||||
<string name="title_sub_update">更新訂閱</string>
|
||||
<string name="title_ping_all_server">偵測所有組態 Tcping</string>
|
||||
<string name="title_real_ping_all_server">偵測所有組態真延遲</string>
|
||||
<string name="title_user_asset_setting">Geo 資源檔案</string>
|
||||
<string name="title_sort_by_test_results">依偵測結果排序</string>
|
||||
<string name="title_filter_config">過濾組態</string>
|
||||
<string name="filter_config_all">所有訂閱分組</string>
|
||||
|
||||
<string name="tasker_start_service">啟動服務</string>
|
||||
<string name="tasker_setting_confirm">確定</string>
|
||||
|
||||
<string name="routing_settings_title">轉送設定</string>
|
||||
<string name="routing_settings_tips">以半形逗號「,」分隔,並手動儲存</string>
|
||||
<string name="routing_settings_save">儲存</string>
|
||||
<string name="routing_settings_delete">清除</string>
|
||||
<string name="routing_settings_scan_replace">掃描並取代</string>
|
||||
<string name="routing_settings_scan_append">掃描並附加</string>
|
||||
<string name="routing_settings_default_rules">設定預設轉送規則</string>
|
||||
|
||||
<string name="connection_test_pending">"測試連線能力"</string>
|
||||
<string name="connection_test_testing">"測試中……"</string>
|
||||
<string name="connection_test_available">"成功:%d ms延遲"</string>
|
||||
<string name="connection_test_error">"測試網際網路連線失敗:%s"</string>
|
||||
<string name="connection_test_fail">"無法使用網際網路"</string>
|
||||
<string name="connection_test_error_status_code">"錯誤碼:(#%d)"</string>
|
||||
<string name="connection_connected">"已連線,輕觸以檢查連線能力"</string>
|
||||
<string name="connection_not_connected">"未連線"</string>
|
||||
|
||||
<string-array name="share_method">
|
||||
<item>QR Code</item>
|
||||
<item>匯出至剪貼簿</item>
|
||||
<item>匯出完整組態至剪貼簿</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>Proxy URL 或 IP</item>
|
||||
<item>直接連線 URL 或 IP</item>
|
||||
<item>已封鎖的 URL 或 IP</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_mode">
|
||||
<item>全域 Proxy</item>
|
||||
<item>略過區域網路的 Proxy</item>
|
||||
<item>略過中國大陸的 Proxy</item>
|
||||
<item>略過區域網路及中國大陸的 Proxy</item>
|
||||
<item>直接連線</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>僅 Proxy</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
229
ExtRes/values/strings.xml
Normal file
229
ExtRes/values/strings.xml
Normal file
@@ -0,0 +1,229 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">v2rayNG</string>
|
||||
<string name="app_widget_name">Switch</string>
|
||||
<string name="app_tile_name">Switch</string>
|
||||
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
<string name="migration_success">Data migration success!</string>
|
||||
<string name="migration_fail">Data migration failed!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">Stop</string>
|
||||
<string name="toast_permission_denied">Unable to obtain the permission</string>
|
||||
<string name="notification_action_more">click for more</string>
|
||||
<string name="toast_services_start">Start Services</string>
|
||||
<string name="toast_services_stop">Stop Services</string>
|
||||
<string name="toast_services_success">Start Services Success</string>
|
||||
<string name="toast_services_failure">Start Services Failure</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">Configuration file</string>
|
||||
<string name="menu_item_add_config">Add config</string>
|
||||
<string name="menu_item_save_config">Save config</string>
|
||||
<string name="menu_item_del_config">Delete config</string>
|
||||
<string name="menu_item_import_config_qrcode">Import config from QRcode</string>
|
||||
<string name="menu_item_import_config_clipboard">Import config from Clipboard</string>
|
||||
<string name="menu_item_import_config_manually_vmess">Type manually[Vmess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">Type manually[VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">Type manually[Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">Type manually[Socks]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">Type manually[Trojan]</string>
|
||||
<string name="menu_item_import_config_custom">custom config</string>
|
||||
<string name="menu_item_import_config_custom_clipboard">Import custom config from Clipboard</string>
|
||||
<string name="menu_item_import_config_custom_local">Import custom config from locally</string>
|
||||
<string name="menu_item_import_config_custom_url">Import custom config from URL</string>
|
||||
<string name="menu_item_import_config_custom_url_scan">Import custom config scan URL</string>
|
||||
<string name="del_config_comfirm">Confirm delete?</string>
|
||||
<string name="server_lab_remarks">remarks</string>
|
||||
<string name="server_lab_address">address</string>
|
||||
<string name="server_lab_port">port</string>
|
||||
<string name="server_lab_id">id</string>
|
||||
<string name="server_lab_alterid">alterId</string>
|
||||
<string name="server_lab_security">security</string>
|
||||
<string name="server_lab_network">Network</string>
|
||||
<string name="server_lab_more_function">Transport</string>
|
||||
<string name="server_lab_head_type">head type</string>
|
||||
<string name="server_lab_mode_type">gRPC mode</string>
|
||||
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC key/kcp seed/gRPC serviceName</string>
|
||||
<string name="server_lab_stream_security">tls</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">address</string>
|
||||
<string name="server_lab_port3">port</string>
|
||||
<string name="server_lab_id3">password</string>
|
||||
<string name="server_lab_security3">security</string>
|
||||
<string name="server_lab_id4">Password(Optional)</string>
|
||||
<string name="server_lab_security4">User(Optional)</string>
|
||||
<string name="server_lab_encryption">encryption</string>
|
||||
<string name="server_lab_flow">flow</string>
|
||||
<string name="toast_success">Success</string>
|
||||
<string name="toast_failure">Failure</string>
|
||||
<string name="toast_none_data">There is nothing</string>
|
||||
<string name="toast_incorrect_protocol">Incorrect protocol</string>
|
||||
<string name="toast_decoding_failed">Decoding failed</string>
|
||||
<string name="title_file_chooser">Select a Config File</string>
|
||||
<string name="toast_require_file_manager">Please install a File Manager.</string>
|
||||
<string name="server_customize_config">Customize Config</string>
|
||||
<string name="toast_config_file_invalid">Invalid Config</string>
|
||||
<string name="server_lab_content">Content</string>
|
||||
<string name="toast_none_data_clipboard">There is no data in the clipboard</string>
|
||||
<string name="toast_invalid_url">Invalid URL</string>
|
||||
<string name="server_lab_need_inbound">Ensure inbounds port is consistent with the settings</string>
|
||||
<string name="toast_malformed_josn">Config malformed</string>
|
||||
<string name="server_lab_request_host6">Host(SNI)(Optional)</string>
|
||||
<string name="toast_asset_copy_failed">File copy failed, please use File Manager</string>
|
||||
<string name="menu_item_add_file">Add files</string>
|
||||
<string name="menu_item_download_file">Download files</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">Loading</string>
|
||||
<string name="menu_item_search">Search</string>
|
||||
<string name="menu_item_select_all">Select all</string>
|
||||
<string name="msg_enter_keywords">Enter keywords</string>
|
||||
<string name="switch_bypass_apps_mode">Bypass Mode</string>
|
||||
<string name="menu_item_select_proxy_app">Auto select proxy app</string>
|
||||
<string name="msg_downloading_content">Downloading content</string>
|
||||
<string name="menu_item_export_proxy_app">Export to Clipboard</string>
|
||||
<string name="menu_item_import_proxy_app">Import from Clipboard</string>
|
||||
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">Settings</string>
|
||||
<string name="title_advanced">Advanced Settings</string>
|
||||
<string name="title_vpn_settings">VPN Settings</string>
|
||||
<string name="title_pref_per_app_proxy">Per-app proxy</string>
|
||||
<string name="summary_pref_per_app_proxy">General: Checked App is proxy, unchecked direct connection; \nbypass mode: checked app directly connected, unchecked proxy. \nThe option to automatically select the proxy application in the menu</string>
|
||||
|
||||
<string name="title_pref_mux_enabled">Enable Mux</string>
|
||||
<string name="summary_pref_mux_enabled">Enable maybe speed up network and switch network maybe flash</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">Enable speed display</string>
|
||||
<string name="summary_pref_speed_enabled">Display current speed in the notification.\nNotification icon would change based on
|
||||
usage.</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">Enable Sniffing</string>
|
||||
<string name="summary_pref_sniffing_enabled">Try sniff domain from the packet (default on)</string>
|
||||
|
||||
<string name="title_pref_local_dns_enabled">Enable local DNS</string>
|
||||
<string name="summary_pref_local_dns_enabled">DNS processed by core‘s DNS module (Recommended, if need routing Bypassing LAN and
|
||||
mainland address)</string>
|
||||
|
||||
<string name="title_pref_fake_dns_enabled">Enable fake DNS</string>
|
||||
<string name="summary_pref_fake_dns_enabled">local DNS returns fake IP address (faster, but it may not work for some apps)</string>
|
||||
|
||||
<string name="title_pref_prefer_ipv6">Prefer IPv6</string>
|
||||
<string name="summary_pref_prefer_ipv6">Prefer IPv6 address and routes</string>
|
||||
|
||||
<string name="title_pref_routing">Routing</string>
|
||||
<string name="title_pref_routing_domain_strategy">Domain strategy</string>
|
||||
<string name="title_pref_routing_mode">Predefined rules</string>
|
||||
<string name="title_pref_routing_custom">Custom rules</string>
|
||||
|
||||
<string name="title_pref_remote_dns">Remote DNS (Optional)</string>
|
||||
<string name="summary_pref_remote_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_vpn_dns">VPN DNS (only IPv4/v6)</string>
|
||||
|
||||
<string name="title_pref_domestic_dns">Domestic DNS (Optional)</string>
|
||||
<string name="summary_pref_domestic_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">Allow connections from the LAN</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">Other devices can connect to proxy by your ip address through socks/http, Only enable in trusted network to avoid unauthorized connection</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">Allow connections from the LAN, Make sure you are in a trusted network</string>
|
||||
|
||||
<string name="title_pref_allow_insecure">allowInsecure</string>
|
||||
<string name="summary_pref_allow_insecure">When TLS, the default allowInsecure</string>
|
||||
|
||||
<string name="title_pref_socks_port">SOCKS5 proxy port</string>
|
||||
<string name="summary_pref_socks_port">SOCKS5 proxy port</string>
|
||||
|
||||
<string name="title_pref_http_port">HTTP proxy port</string>
|
||||
<string name="summary_pref_http_port">HTTP proxy port</string>
|
||||
|
||||
<string name="title_pref_local_dns_port">Local DNS port</string>
|
||||
<string name="summary_pref_local_dns_port">Local DNS port</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">Delete configuration file confirmation</string>
|
||||
<string name="summary_pref_confirm_remove">Whether to delete the configuration file requires a second confirmation by the user</string>
|
||||
|
||||
<string name="title_pref_feedback">Feedback</string>
|
||||
<string name="summary_pref_feedback">Feedback enhancements or bugs to GitHub</string>
|
||||
<string name="summary_pref_tg_group">Join Telegram Group</string>
|
||||
<string name="toast_tg_app_not_found">Telegram app not found</string>
|
||||
|
||||
<string name="title_pref_promotion">Promotion</string>
|
||||
<string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string>
|
||||
|
||||
<string name="title_core_loglevel">Log Level</string>
|
||||
<string name="title_mode">Mode</string>
|
||||
<string name="title_mode_help">Click me for more help</string>
|
||||
<string name="title_language">Language</string>
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">Copy</string>
|
||||
<string name="logcat_clear">Clear</string>
|
||||
<string name="title_service_restart">Service restart</string>
|
||||
<string name="title_del_all_config">Delete all config</string>
|
||||
<string name="title_del_invalid_config">Delete invalid config(Test first)</string>
|
||||
<string name="title_export_all">Export non-custom configs to clipboard</string>
|
||||
<string name="title_sub_setting">Subscription group setting</string>
|
||||
<string name="sub_setting_remarks">remarks</string>
|
||||
<string name="sub_setting_url">Optional URL</string>
|
||||
<string name="sub_setting_enable">enable update</string>
|
||||
<string name="title_sub_update">Update subscription</string>
|
||||
<string name="title_ping_all_server">Tcping all configuration</string>
|
||||
<string name="title_real_ping_all_server">Real delay all configuration</string>
|
||||
<string name="title_user_asset_setting">Geo asset files</string>
|
||||
<string name="title_sort_by_test_results">Sorting by test results</string>
|
||||
<string name="title_filter_config">Filter configuration file</string>
|
||||
<string name="filter_config_all">All subscription groups</string>
|
||||
|
||||
<string name="tasker_start_service">Start Service</string>
|
||||
<string name="tasker_setting_confirm">Confirm</string>
|
||||
|
||||
<string name="routing_settings_title">Routing Settings</string>
|
||||
<string name="routing_settings_tips">Separated by commas(,),remember to save</string>
|
||||
<string name="routing_settings_save">Save</string>
|
||||
<string name="routing_settings_delete">Clear</string>
|
||||
<string name="routing_settings_scan_replace">Scan and replace</string>
|
||||
<string name="routing_settings_scan_append">Scan and append</string>
|
||||
<string name="routing_settings_default_rules"> set default routing rules</string>
|
||||
|
||||
<string name="connection_test_pending">Check Connectivity</string>
|
||||
<string name="connection_test_testing">Testing…</string>
|
||||
<string name="connection_test_available">Success: HTTP connection took %dms</string>
|
||||
<string name="connection_test_error">Fail to detect internet connection: %s</string>
|
||||
<string name="connection_test_fail">Internet Unavailable</string>
|
||||
<string name="connection_test_error_status_code">Error code: #%d</string>
|
||||
<string name="connection_connected">Connected, tap to check connection</string>
|
||||
<string name="connection_not_connected">Not connected</string>
|
||||
|
||||
<string-array name="share_method">
|
||||
<item>QRcode</item>
|
||||
<item>Export to clipboard</item>
|
||||
<item>Export full configuration to clipboard</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_tag">
|
||||
<item>proxy URL or IP</item>
|
||||
<item>direct URL or IP</item>
|
||||
<item>blocked URL or IP</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="routing_mode">
|
||||
<item>Global proxy</item>
|
||||
<item>Bypassing the LAN address then proxy</item>
|
||||
<item>Bypass mainland address then proxy</item>
|
||||
<item>Bypassing LAN and mainland address then proxy</item>
|
||||
<item>Global direct</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>Proxy only</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
18
README.md
18
README.md
@@ -1,12 +1,13 @@
|
||||
# v2rayNG
|
||||
|
||||
A V2Ray client for Android
|
||||
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://developer.android.com/about/versions/lollipop)
|
||||
[](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">
|
||||
<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" />
|
||||
@@ -14,7 +15,13 @@ A V2Ray client for Android
|
||||
|
||||
### Usage
|
||||
|
||||
See our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
#### Geoip and Geosite
|
||||
- geoip.dat and geosite.dat files are in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
|
||||
- download feature will get enhanced version in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (Note it need a working proxy)
|
||||
- latest official [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip) can be imported manually
|
||||
- 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)
|
||||
|
||||
### More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
|
||||
### Development guide
|
||||
|
||||
@@ -22,4 +29,5 @@ Android project under V2rayNG folder can be compiled directly in Android Studio,
|
||||
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
|
||||
v2rayNG can run on Android Emulators. For WSA, VPN permission need to be granted via
|
||||
`appops set [package name] ACTIVATE_VPN allow`
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||
@@ -13,7 +12,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.v2ray.ang"
|
||||
minSdkVersion 17
|
||||
minSdkVersion 21
|
||||
targetSdkVersion Integer.parseInt("$targetSdkVer")
|
||||
multiDexEnabled true
|
||||
versionCode 212
|
||||
@@ -25,12 +24,14 @@ android {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +39,10 @@ android {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
@@ -53,65 +58,64 @@ android {
|
||||
android.applicationVariants.all { variant ->
|
||||
// assign different version code for each output
|
||||
variant.outputs.each { output ->
|
||||
output.versionCodeOverride =
|
||||
output.outputFileName = "v2rayNG_" + variant.versionName + "_" + output.getFilter(com.android.build.OutputFile.ABI) + ".apk"
|
||||
|
||||
output.versionCodeOverride =
|
||||
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
|
||||
1000000 + android.defaultConfig.versionCode
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
testImplementation 'junit:junit:4.13'
|
||||
implementation project(':dpreference')
|
||||
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
// Androidx
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
||||
implementation "androidx.appcompat:appcompat:1.4.1"
|
||||
implementation "com.google.android.material:material:1.5.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation "androidx.preference:preference:1.0.0"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
|
||||
// Androidx ktx
|
||||
implementation 'androidx.activity:activity-ktx:1.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
|
||||
|
||||
//kotlin
|
||||
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"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||
|
||||
// Android support library
|
||||
implementation "com.android.support:support-v4:$supportLibVersion"
|
||||
implementation "com.android.support:appcompat-v7:$supportLibVersion"
|
||||
implementation "com.android.support:design:$supportLibVersion"
|
||||
implementation "com.android.support:cardview-v7:$supportLibVersion"
|
||||
implementation "com.android.support:preference-v7:$supportLibVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
|
||||
implementation "com.android.support:multidex:1.0.3"
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
|
||||
|
||||
// DSL
|
||||
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
|
||||
implementation 'com.tencent:mmkv-static:1.2.12'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'io.reactivex:rxjava:1.3.4'
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
|
||||
implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
|
||||
implementation 'io.reactivex:rxkotlin:0.60.0'
|
||||
implementation 'me.dm7.barcodescanner:core:1.9.8'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
||||
implementation 'com.beust:klaxon:3.0.1'
|
||||
|
||||
implementation(name: 'libv2ray', ext: 'aar')
|
||||
//implementation(name: 'tun2socks', ext: 'aar')
|
||||
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||
implementation 'com.blacksquircle.ui:editorkit:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:language-base:2.1.1'
|
||||
implementation 'com.blacksquircle.ui:language-json:2.1.1'
|
||||
}
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url 'https://maven.google.com' }
|
||||
}
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
maven { url 'https://jitpack.io' }
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
-dontwarn org.jetbrains.anko.internals.**
|
||||
-keep class org.jetbrains.anko.internals.** { *;}
|
||||
|
||||
-dontwarn rx.internal.util.unsafe.**
|
||||
-keep class rx.internal.util.unsafe.** { *;}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.v2ray.ang">
|
||||
|
||||
<supports-screens
|
||||
@@ -12,6 +13,11 @@
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
|
||||
|
||||
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@@ -21,62 +27,72 @@
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
|
||||
<application
|
||||
android:name=".AngApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="m">
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".ui.MainActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ServerActivity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:name=".ui.Server2Activity"
|
||||
android:exported="false"
|
||||
android:name=".ui.ServerCustomConfigActivity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity
|
||||
android:name=".ui.Server3Activity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
android:exported="false"
|
||||
android:name=".ui.SettingsActivity" />
|
||||
<activity
|
||||
android:name=".ui.Server4Activity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity android:name=".ui.SettingsActivity" />
|
||||
<activity android:name=".ui.PerAppProxyActivity" />
|
||||
<activity android:name=".ui.ScannerActivity" />
|
||||
<!-- <activity android:name=".InappBuyActivity" />-->
|
||||
<activity android:name=".ui.LogcatActivity" />
|
||||
android:exported="false"
|
||||
android:name=".ui.PerAppProxyActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ScannerActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.LogcatActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.RoutingSettingsActivity"
|
||||
android:windowSoftInputMode="stateUnchanged" />
|
||||
<activity android:name=".ui.SubSettingActivity" />
|
||||
|
||||
<activity android:name=".ui.SubEditActivity" />
|
||||
<activity android:name=".ui.ScScannerActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.SubSettingActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.SubEditActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ScScannerActivity" />
|
||||
<activity
|
||||
android:exported="false"
|
||||
android:name=".ui.ScSwitchActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:process=":RunSoLibV2RayDaemon"
|
||||
@@ -103,7 +119,9 @@
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
</service>
|
||||
|
||||
<receiver android:name=".receiver.WidgetProvider"
|
||||
<receiver
|
||||
android:exported="true"
|
||||
android:name=".receiver.WidgetProvider"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
@@ -116,6 +134,7 @@
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:exported="true"
|
||||
android:name=".service.QSTileService"
|
||||
android:icon="@drawable/ic_v"
|
||||
android:label="@string/app_tile_name"
|
||||
@@ -127,6 +146,7 @@
|
||||
</service>
|
||||
<!-- =====================Tasker===================== -->
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name=".ui.TaskerActivity"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
@@ -135,7 +155,9 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.TaskerReceiver"
|
||||
<receiver
|
||||
android:exported="true"
|
||||
android:name=".receiver.TaskerReceiver"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
|
||||
@@ -54,7 +54,6 @@
|
||||
"users": [
|
||||
{
|
||||
"id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
|
||||
"alterId": 64,
|
||||
"security": "auto",
|
||||
"level": 8
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
/**
|
||||
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
/**
|
||||
* Interface to notify an item ViewHolder of relevant callbacks from {@link
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
/**
|
||||
* Listener for manual initiation of a drag.
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package com.v2ray.ang.helper;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.helper.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
|
||||
@@ -52,7 +54,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
public int getMovementFlags(RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
|
||||
// Set movement flags based on the layout manager
|
||||
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
|
||||
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
|
||||
@@ -66,24 +68,25 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
|
||||
public boolean onMove(@NotNull RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
|
||||
if (source.getItemViewType() != target.getItemViewType()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Notify the adapter of the move
|
||||
mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
|
||||
mAdapter.onItemMove(source.getBindingAdapterPosition(), target.getBindingAdapterPosition());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
|
||||
// Notify the adapter of the dismissal
|
||||
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
|
||||
mAdapter.onItemDismiss(viewHolder.getBindingAdapterPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||
public void onChildDraw(@NotNull Canvas c, @NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder, float dX,
|
||||
float dY, int actionState, boolean isCurrentlyActive) {
|
||||
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
|
||||
// Fade out the view as it is swiped out of the parent's bounds
|
||||
final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
|
||||
@@ -109,7 +112,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
public void clearView(@NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
|
||||
super.clearView(recyclerView, viewHolder);
|
||||
|
||||
mAdapter.onItemMoveCompleted();
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class AssetsUtil {
|
||||
public static boolean copyAssetFolder(AssetManager assetManager,
|
||||
String fromAssetPath, String toPath) {
|
||||
try {
|
||||
String[] files = assetManager.list(fromAssetPath);
|
||||
new File(toPath).mkdirs();
|
||||
boolean res = true;
|
||||
for (String file : files)
|
||||
if (file.contains("."))
|
||||
res &= copyAsset(assetManager,
|
||||
fromAssetPath + "/" + file,
|
||||
toPath + "/" + file);
|
||||
else
|
||||
res &= copyAssetFolder(assetManager,
|
||||
fromAssetPath + "/" + file,
|
||||
toPath + "/" + file);
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean copyAsset(AssetManager assetManager,
|
||||
String fromAssetPath, String toPath) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = assetManager.open(fromAssetPath);
|
||||
new File(toPath).createNewFile();
|
||||
out = new FileOutputStream(toPath);
|
||||
copyFile(in, out);
|
||||
in.close();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String readTextFromAssets(AssetManager assetManager, String fileName) {
|
||||
try {
|
||||
InputStreamReader inputReader = new InputStreamReader(assetManager.open(fileName));
|
||||
BufferedReader bufReader = new BufferedReader(inputReader);
|
||||
String line;
|
||||
String Result = "";
|
||||
while ((line = bufReader.readLine()) != null)
|
||||
Result += line;
|
||||
return Result;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getAssetPath(Context context, String assetPath) {
|
||||
InputStream in = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
context.deleteFile(assetPath);
|
||||
|
||||
in = context.getAssets().open(assetPath);
|
||||
out = context.openFileOutput(assetPath, MODE_PRIVATE);
|
||||
copyFile(in, out);
|
||||
in.close();
|
||||
|
||||
String path = context.getFilesDir().toString();
|
||||
return path + "/" + assetPath;
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return "";
|
||||
} finally {
|
||||
try {
|
||||
if (out != null) {
|
||||
out.close();
|
||||
}
|
||||
if (in != null) {
|
||||
in.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyFile(InputStream in, OutputStream out) throws IOException {
|
||||
byte[] buffer = new byte[1024];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,570 +0,0 @@
|
||||
// Portions copyright 2002, Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
// This code was converted from code at http://iharder.sourceforge.net/base64/
|
||||
// Lots of extraneous features were removed.
|
||||
/* The original code said:
|
||||
* <p>
|
||||
* I am placing this code in the Public Domain. Do with it as you will.
|
||||
* This software comes with no guarantees or warranties but with
|
||||
* plenty of well-wishing instead!
|
||||
* Please visit
|
||||
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
|
||||
* periodically to check for updates or to contribute improvements.
|
||||
* </p>
|
||||
*
|
||||
* @author Robert Harder
|
||||
* @author rharder@usa.net
|
||||
* @version 1.3
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base64 converter class. This code is not a complete MIME encoder;
|
||||
* it simply converts binary data to base64 data and back.
|
||||
*
|
||||
* <p>Note {@link CharBase64} is a GWT-compatible implementation of this
|
||||
* class.
|
||||
*/
|
||||
public class Base64 {
|
||||
/** Specify encoding (value is {@code true}). */
|
||||
public final static boolean ENCODE = true;
|
||||
|
||||
/** Specify decoding (value is {@code false}). */
|
||||
public final static boolean DECODE = false;
|
||||
|
||||
/** The equals sign (=) as a byte. */
|
||||
private final static byte EQUALS_SIGN = (byte) '=';
|
||||
|
||||
/** The new line character (\n) as a byte. */
|
||||
private final static byte NEW_LINE = (byte) '\n';
|
||||
|
||||
/**
|
||||
* The 64 valid Base64 values.
|
||||
*/
|
||||
private final static byte[] ALPHABET =
|
||||
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
|
||||
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
|
||||
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
|
||||
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
|
||||
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
|
||||
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
|
||||
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
|
||||
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
|
||||
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
|
||||
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
|
||||
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
|
||||
(byte) '9', (byte) '+', (byte) '/'};
|
||||
|
||||
/**
|
||||
* The 64 valid web safe Base64 values.
|
||||
*/
|
||||
private final static byte[] WEBSAFE_ALPHABET =
|
||||
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
|
||||
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
|
||||
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
|
||||
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
|
||||
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
|
||||
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
|
||||
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
|
||||
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
|
||||
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
|
||||
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
|
||||
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
|
||||
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
|
||||
(byte) '9', (byte) '-', (byte) '_'};
|
||||
|
||||
/**
|
||||
* Translates a Base64 value to either its 6-bit reconstruction value
|
||||
* or a negative number indicating some other meaning.
|
||||
**/
|
||||
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
|
||||
62, // Plus sign at decimal 43
|
||||
-9, -9, -9, // Decimal 44 - 46
|
||||
63, // Slash at decimal 47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
|
||||
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
|
||||
};
|
||||
|
||||
/** The web safe decodabet */
|
||||
private final static byte[] WEBSAFE_DECODABET =
|
||||
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
|
||||
-5, -5, // Whitespace: Tab and Linefeed
|
||||
-9, -9, // Decimal 11 - 12
|
||||
-5, // Whitespace: Carriage Return
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
|
||||
-9, -9, -9, -9, -9, // Decimal 27 - 31
|
||||
-5, // Whitespace: Space
|
||||
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
|
||||
62, // Dash '-' sign at decimal 45
|
||||
-9, -9, // Decimal 46-47
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
|
||||
-9, -9, -9, // Decimal 58 - 60
|
||||
-1, // Equals sign at decimal 61
|
||||
-9, -9, -9, // Decimal 62 - 64
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
|
||||
-9, -9, -9, -9, // Decimal 91-94
|
||||
63, // Underscore '_' at decimal 95
|
||||
-9, // Decimal 96
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
|
||||
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
|
||||
-9, -9, -9, -9, -9 // Decimal 123 - 127
|
||||
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
|
||||
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
|
||||
};
|
||||
|
||||
// Indicates white space in encoding
|
||||
private final static byte WHITE_SPACE_ENC = -5;
|
||||
// Indicates equals sign in encoding
|
||||
private final static byte EQUALS_SIGN_ENC = -1;
|
||||
|
||||
/** Defeats instantiation. */
|
||||
private Base64() {
|
||||
}
|
||||
|
||||
/* ******** E N C O D I N G M E T H O D S ******** */
|
||||
|
||||
/**
|
||||
* Encodes up to three bytes of the array <var>source</var>
|
||||
* and writes the resulting four Base64 bytes to <var>destination</var>.
|
||||
* The source and destination arrays can be manipulated
|
||||
* anywhere along their length by specifying
|
||||
* <var>srcOffset</var> and <var>destOffset</var>.
|
||||
* This method does not check to make sure your arrays
|
||||
* are large enough to accommodate <var>srcOffset</var> + 3 for
|
||||
* the <var>source</var> array or <var>destOffset</var> + 4 for
|
||||
* the <var>destination</var> array.
|
||||
* The actual number of significant bytes in your array is
|
||||
* given by <var>numSigBytes</var>.
|
||||
*
|
||||
* @param source the array to convert
|
||||
* @param srcOffset the index where conversion begins
|
||||
* @param numSigBytes the number of significant bytes in your array
|
||||
* @param destination the array to hold the conversion
|
||||
* @param destOffset the index where output will be put
|
||||
* @param alphabet is the encoding alphabet
|
||||
* @return the <var>destination</var> array
|
||||
* @since 1.3
|
||||
*/
|
||||
private static byte[] encode3to4(byte[] source, int srcOffset,
|
||||
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901 Bit position
|
||||
// --------000000001111111122222222 Array position from threeBytes
|
||||
// --------| || || || | Six bit groups to index alphabet
|
||||
// >>18 >>12 >> 6 >> 0 Right shift necessary
|
||||
// 0x3f 0x3f 0x3f Additional AND
|
||||
|
||||
// Create buffer with zero-padding if there are only one or two
|
||||
// significant bytes passed in the array.
|
||||
// We have to shift left 24 in order to flush out the 1's that appear
|
||||
// when Java treats a value as negative that is cast from a byte to an int.
|
||||
int inBuff =
|
||||
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
|
||||
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
|
||||
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
|
||||
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
|
||||
return destination;
|
||||
case 2:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
case 1:
|
||||
destination[destOffset] = alphabet[(inBuff >>> 18)];
|
||||
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
destination[destOffset + 2] = EQUALS_SIGN;
|
||||
destination[destOffset + 3] = EQUALS_SIGN;
|
||||
return destination;
|
||||
default:
|
||||
return destination;
|
||||
} // end switch
|
||||
} // end encode3to4
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
* Equivalent to calling
|
||||
* {@code encodeBytes(source, 0, source.length)}
|
||||
*
|
||||
* @param source The data to convert
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
return encode(source, 0, source.length, ALPHABET, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into web safe Base64 notation.
|
||||
*
|
||||
* @param source The data to convert
|
||||
* @param doPadding is {@code true} to pad result with '=' chars
|
||||
* if it does not fall on 3 byte boundaries
|
||||
*/
|
||||
public static String encodeWebSafe(byte[] source, boolean doPadding) {
|
||||
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source the data to convert
|
||||
* @param off offset in array where conversion should begin
|
||||
* @param len length of data to convert
|
||||
* @param alphabet the encoding alphabet
|
||||
* @param doPadding is {@code true} to pad result with '=' chars
|
||||
* if it does not fall on 3 byte boundaries
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encode(byte[] source, int off, int len, byte[] alphabet,
|
||||
boolean doPadding) {
|
||||
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
|
||||
int outLen = outBuff.length;
|
||||
|
||||
// If doPadding is false, set length to truncate '='
|
||||
// padding characters
|
||||
while (doPadding == false && outLen > 0) {
|
||||
if (outBuff[outLen - 1] != '=') {
|
||||
break;
|
||||
}
|
||||
outLen -= 1;
|
||||
}
|
||||
|
||||
return new String(outBuff, 0, outLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a byte array into Base64 notation.
|
||||
*
|
||||
* @param source the data to convert
|
||||
* @param off offset in array where conversion should begin
|
||||
* @param len length of data to convert
|
||||
* @param alphabet is the encoding alphabet
|
||||
* @param maxLineLength maximum length of one line.
|
||||
* @return the BASE64-encoded byte array
|
||||
*/
|
||||
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
|
||||
int maxLineLength) {
|
||||
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
|
||||
int len43 = lenDiv3 * 4;
|
||||
byte[] outBuff = new byte[len43 // Main 4:3
|
||||
+ (len43 / maxLineLength)]; // New lines
|
||||
|
||||
int d = 0;
|
||||
int e = 0;
|
||||
int len2 = len - 2;
|
||||
int lineLength = 0;
|
||||
for (; d < len2; d += 3, e += 4) {
|
||||
|
||||
// The following block of code is the same as
|
||||
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
|
||||
// but inlined for faster encoding (~20% improvement)
|
||||
int inBuff =
|
||||
((source[d + off] << 24) >>> 8)
|
||||
| ((source[d + 1 + off] << 24) >>> 16)
|
||||
| ((source[d + 2 + off] << 24) >>> 24);
|
||||
outBuff[e] = alphabet[(inBuff >>> 18)];
|
||||
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
|
||||
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
|
||||
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
lineLength = 0;
|
||||
} // end if: end of line
|
||||
} // end for: each piece of array
|
||||
|
||||
if (d < len) {
|
||||
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
|
||||
|
||||
lineLength += 4;
|
||||
if (lineLength == maxLineLength) {
|
||||
// Add a last newline
|
||||
outBuff[e + 4] = NEW_LINE;
|
||||
e++;
|
||||
}
|
||||
e += 4;
|
||||
}
|
||||
|
||||
assert (e == outBuff.length);
|
||||
return outBuff;
|
||||
}
|
||||
|
||||
|
||||
/* ******** D E C O D I N G M E T H O D S ******** */
|
||||
|
||||
|
||||
/**
|
||||
* Decodes four bytes from array <var>source</var>
|
||||
* and writes the resulting bytes (up to three of them)
|
||||
* to <var>destination</var>.
|
||||
* The source and destination arrays can be manipulated
|
||||
* anywhere along their length by specifying
|
||||
* <var>srcOffset</var> and <var>destOffset</var>.
|
||||
* This method does not check to make sure your arrays
|
||||
* are large enough to accommodate <var>srcOffset</var> + 4 for
|
||||
* the <var>source</var> array or <var>destOffset</var> + 3 for
|
||||
* the <var>destination</var> array.
|
||||
* This method returns the actual number of bytes that
|
||||
* were converted from the Base64 encoding.
|
||||
*
|
||||
*
|
||||
* @param source the array to convert
|
||||
* @param srcOffset the index where conversion begins
|
||||
* @param destination the array to hold the conversion
|
||||
* @param destOffset the index where output will be put
|
||||
* @param decodabet the decodabet for decoding Base64 content
|
||||
* @return the number of decoded bytes converted
|
||||
* @since 1.3
|
||||
*/
|
||||
private static int decode4to3(byte[] source, int srcOffset,
|
||||
byte[] destination, int destOffset, byte[] decodabet) {
|
||||
// Example: Dk==
|
||||
if (source[srcOffset + 2] == EQUALS_SIGN) {
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
return 1;
|
||||
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
|
||||
// Example: DkL=
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >>> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >>> 8);
|
||||
return 2;
|
||||
} else {
|
||||
// Example: DkLE
|
||||
int outBuff =
|
||||
((decodabet[source[srcOffset]] << 24) >>> 6)
|
||||
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
|
||||
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
|
||||
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
|
||||
|
||||
destination[destOffset] = (byte) (outBuff >> 16);
|
||||
destination[destOffset + 1] = (byte) (outBuff >> 8);
|
||||
destination[destOffset + 2] = (byte) (outBuff);
|
||||
return 3;
|
||||
}
|
||||
} // end decodeToBytes
|
||||
|
||||
|
||||
/**
|
||||
* Decodes data from Base64 notation.
|
||||
*
|
||||
* @param s the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
* @since 1.4
|
||||
*/
|
||||
public static byte[] decode(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decode(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes data from web safe Base64 notation.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param s the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
|
||||
byte[] bytes = s.getBytes();
|
||||
return decodeWebSafe(bytes, 0, bytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source The Base64 encoded data
|
||||
* @return decoded data
|
||||
* @since 1.3
|
||||
* @throws Base64DecoderException
|
||||
*/
|
||||
public static byte[] decode(byte[] source) throws Base64DecoderException {
|
||||
return decode(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns
|
||||
* the decoded data.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param source the string to decode (decoded in default encoding)
|
||||
* @return the decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source)
|
||||
throws Base64DecoderException {
|
||||
return decodeWebSafe(source, 0, source.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @return decoded data
|
||||
* @since 1.3
|
||||
* @throws Base64DecoderException
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len)
|
||||
throws Base64DecoderException {
|
||||
return decode(source, off, len, DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes web safe Base64 content in byte array format and returns
|
||||
* the decoded byte array.
|
||||
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decodeWebSafe(byte[] source, int off, int len)
|
||||
throws Base64DecoderException {
|
||||
return decode(source, off, len, WEBSAFE_DECODABET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes Base64 content using the supplied decodabet and returns
|
||||
* the decoded byte array.
|
||||
*
|
||||
* @param source the Base64 encoded data
|
||||
* @param off the offset of where to begin decoding
|
||||
* @param len the length of characters to decode
|
||||
* @param decodabet the decodabet for decoding Base64 content
|
||||
* @return decoded data
|
||||
*/
|
||||
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
|
||||
throws Base64DecoderException {
|
||||
int len34 = len * 3 / 4;
|
||||
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
|
||||
int outBuffPosn = 0;
|
||||
|
||||
byte[] b4 = new byte[4];
|
||||
int b4Posn = 0;
|
||||
int i = 0;
|
||||
byte sbiCrop = 0;
|
||||
byte sbiDecode = 0;
|
||||
for (i = 0; i < len; i++) {
|
||||
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
|
||||
sbiDecode = decodabet[sbiCrop];
|
||||
|
||||
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
|
||||
if (sbiDecode >= EQUALS_SIGN_ENC) {
|
||||
// An equals sign (for padding) must not occur at position 0 or 1
|
||||
// and must be the last byte[s] in the encoded value
|
||||
if (sbiCrop == EQUALS_SIGN) {
|
||||
int bytesLeft = len - i;
|
||||
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
|
||||
if (b4Posn == 0 || b4Posn == 1) {
|
||||
throw new Base64DecoderException(
|
||||
"invalid padding byte '=' at byte offset " + i);
|
||||
} else if ((b4Posn == 3 && bytesLeft > 2)
|
||||
|| (b4Posn == 4 && bytesLeft > 1)) {
|
||||
throw new Base64DecoderException(
|
||||
"padding byte '=' falsely signals end of encoded value "
|
||||
+ "at offset " + i);
|
||||
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
|
||||
throw new Base64DecoderException(
|
||||
"encoded value has invalid trailing byte");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
b4[b4Posn++] = sbiCrop;
|
||||
if (b4Posn == 4) {
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
b4Posn = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Base64DecoderException("Bad Base64 input character at " + i
|
||||
+ ": " + source[i + off] + "(decimal)");
|
||||
}
|
||||
}
|
||||
|
||||
// Because web safe encoding allows non padding base64 encodes, we
|
||||
// need to pad the rest of the b4 buffer with equal signs when
|
||||
// b4Posn != 0. There can be at most 2 equal signs at the end of
|
||||
// four characters, so the b4 buffer must have two or three
|
||||
// characters. This also catches the case where the input is
|
||||
// padded with EQUALS_SIGN
|
||||
if (b4Posn != 0) {
|
||||
if (b4Posn == 1) {
|
||||
throw new Base64DecoderException("single trailing character at offset "
|
||||
+ (len - 1));
|
||||
}
|
||||
b4[b4Posn++] = EQUALS_SIGN;
|
||||
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
|
||||
}
|
||||
|
||||
byte[] out = new byte[outBuffPosn];
|
||||
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2002, Google, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
/**
|
||||
* Exception thrown when encountering an invalid Base64 input character.
|
||||
*
|
||||
* @author nelson
|
||||
*/
|
||||
public class Base64DecoderException extends Exception {
|
||||
public Base64DecoderException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Base64DecoderException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
/**
|
||||
* Exception thrown when something went wrong with in-app billing.
|
||||
* An IabException has an associated IabResult (an error).
|
||||
* To get the IAB result that caused this exception to be thrown,
|
||||
* call {@link #getResult()}.
|
||||
*/
|
||||
public class IabException extends Exception {
|
||||
IabResult mResult;
|
||||
|
||||
public IabException(IabResult r) {
|
||||
this(r, null);
|
||||
}
|
||||
public IabException(int response, String message) {
|
||||
this(new IabResult(response, message));
|
||||
}
|
||||
public IabException(IabResult r, Exception cause) {
|
||||
super(r.getMessage(), cause);
|
||||
mResult = r;
|
||||
}
|
||||
public IabException(int response, String message, Exception cause) {
|
||||
this(new IabResult(response, message), cause);
|
||||
}
|
||||
|
||||
/** Returns the IAB result (error) that this exception signals. */
|
||||
public IabResult getResult() { return mResult; }
|
||||
}
|
||||
@@ -1,979 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender.SendIntentException;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.vending.billing.IInAppBillingService;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Provides convenience methods for in-app billing. You can create one instance of this
|
||||
* class for your application and use it to process in-app billing operations.
|
||||
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
|
||||
* many common in-app billing operations, as well as automatic signature
|
||||
* verification.
|
||||
* <p>
|
||||
* After instantiating, you must perform setup in order to start using the object.
|
||||
* To perform setup, call the {@link #startSetup} method and provide a listener;
|
||||
* that listener will be notified when setup is complete, after which (and not before)
|
||||
* you may call other methods.
|
||||
* <p>
|
||||
* After setup is complete, you will typically want to request an inventory of owned
|
||||
* items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
|
||||
* and related methods.
|
||||
* <p>
|
||||
* When you are done with this object, don't forget to call {@link #dispose}
|
||||
* to ensure proper cleanup. This object holds a binding to the in-app billing
|
||||
* service, which will leak unless you dispose of it correctly. If you created
|
||||
* the object on an Activity's onCreate method, then the recommended
|
||||
* place to dispose of it is the Activity's onDestroy method.
|
||||
* <p>
|
||||
* A note about threading: When using this object from a background thread, you may
|
||||
* call the blocking versions of methods; when using from a UI thread, call
|
||||
* only the asynchronous versions and handle the results via callbacks.
|
||||
* Also, notice that you can only call one asynchronous operation at a time;
|
||||
* attempting to start a second asynchronous operation while the first one
|
||||
* has not yet completed will result in an exception being thrown.
|
||||
*
|
||||
* @author Bruno Oliveira (Google)
|
||||
*/
|
||||
public class IabHelper {
|
||||
// Is debug logging enabled?
|
||||
boolean mDebugLog = false;
|
||||
String mDebugTag = "IabHelper";
|
||||
|
||||
// Is setup done?
|
||||
boolean mSetupDone = false;
|
||||
|
||||
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
|
||||
boolean mDisposed = false;
|
||||
|
||||
// Are subscriptions supported?
|
||||
boolean mSubscriptionsSupported = false;
|
||||
|
||||
// Is an asynchronous operation in progress?
|
||||
// (only one at a time can be in progress)
|
||||
boolean mAsyncInProgress = false;
|
||||
|
||||
// (for logging/debugging)
|
||||
// if mAsyncInProgress == true, what asynchronous operation is in progress?
|
||||
String mAsyncOperation = "";
|
||||
|
||||
// Context we were passed during initialization
|
||||
Context mContext;
|
||||
|
||||
// Connection to the service
|
||||
IInAppBillingService mService;
|
||||
ServiceConnection mServiceConn;
|
||||
|
||||
// The request code used to launch purchase flow
|
||||
int mRequestCode;
|
||||
|
||||
// The item type of the current purchase flow
|
||||
String mPurchasingItemType;
|
||||
|
||||
// Public key for verifying signature, in base64 encoding
|
||||
String mSignatureBase64 = null;
|
||||
|
||||
// Billing response codes
|
||||
public static final int BILLING_RESPONSE_RESULT_OK = 0;
|
||||
public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
|
||||
public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
|
||||
public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
|
||||
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
|
||||
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
|
||||
|
||||
// IAB Helper error codes
|
||||
public static final int IABHELPER_ERROR_BASE = -1000;
|
||||
public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
|
||||
public static final int IABHELPER_BAD_RESPONSE = -1002;
|
||||
public static final int IABHELPER_VERIFICATION_FAILED = -1003;
|
||||
public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
|
||||
public static final int IABHELPER_USER_CANCELLED = -1005;
|
||||
public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
|
||||
public static final int IABHELPER_MISSING_TOKEN = -1007;
|
||||
public static final int IABHELPER_UNKNOWN_ERROR = -1008;
|
||||
public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
|
||||
public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
|
||||
|
||||
// Keys for the responses from InAppBillingService
|
||||
public static final String RESPONSE_CODE = "RESPONSE_CODE";
|
||||
public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
|
||||
public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
|
||||
public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
|
||||
public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
|
||||
public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
|
||||
public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
|
||||
public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
|
||||
public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
|
||||
|
||||
// Item types
|
||||
public static final String ITEM_TYPE_INAPP = "inapp";
|
||||
public static final String ITEM_TYPE_SUBS = "subs";
|
||||
|
||||
// some fields on the getSkuDetails response bundle
|
||||
public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
|
||||
public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
|
||||
|
||||
/**
|
||||
* Creates an instance. After creation, it will not yet be ready to use. You must perform
|
||||
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
|
||||
* block and is safe to call from a UI thread.
|
||||
*
|
||||
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
|
||||
* @param base64PublicKey Your application's public key, encoded in base64.
|
||||
* This is used for verification of purchase signatures. You can find your app's base64-encoded
|
||||
* public key in your application's page on Google Play Developer Console. Note that this
|
||||
* is NOT your "developer public key".
|
||||
*/
|
||||
public IabHelper(Context ctx, String base64PublicKey) {
|
||||
mContext = ctx.getApplicationContext();
|
||||
mSignatureBase64 = base64PublicKey;
|
||||
logDebug("IAB helper created.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disable debug logging through LogCat.
|
||||
*/
|
||||
public void enableDebugLogging(boolean enable, String tag) {
|
||||
checkNotDisposed();
|
||||
mDebugLog = enable;
|
||||
mDebugTag = tag;
|
||||
}
|
||||
|
||||
public void enableDebugLogging(boolean enable) {
|
||||
checkNotDisposed();
|
||||
mDebugLog = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
|
||||
* when the setup process is complete.
|
||||
*/
|
||||
public interface OnIabSetupFinishedListener {
|
||||
/**
|
||||
* Called to notify that setup is complete.
|
||||
*
|
||||
* @param result The result of the setup process.
|
||||
*/
|
||||
void onIabSetupFinished(IabResult result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the setup process. This will start up the setup process asynchronously.
|
||||
* You will be notified through the listener when the setup process is complete.
|
||||
* This method is safe to call from a UI thread.
|
||||
*
|
||||
* @param listener The listener to notify when the setup process is complete.
|
||||
*/
|
||||
public void startSetup(final OnIabSetupFinishedListener listener) {
|
||||
// If already set up, can't do it again.
|
||||
checkNotDisposed();
|
||||
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
|
||||
|
||||
// Connection to IAB service
|
||||
logDebug("Starting in-app billing setup.");
|
||||
mServiceConn = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
logDebug("Billing service disconnected.");
|
||||
mService = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
if (mDisposed) return;
|
||||
logDebug("Billing service connected.");
|
||||
mService = IInAppBillingService.Stub.asInterface(service);
|
||||
String packageName = mContext.getPackageName();
|
||||
try {
|
||||
logDebug("Checking for in-app billing 3 support.");
|
||||
|
||||
// check for in-app billing v3 support
|
||||
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
|
||||
"Error checking for billing v3 support."));
|
||||
|
||||
// if in-app purchases aren't supported, neither are subscriptions.
|
||||
mSubscriptionsSupported = false;
|
||||
return;
|
||||
}
|
||||
logDebug("In-app billing version 3 supported for " + packageName);
|
||||
|
||||
// check for v3 subscriptions support
|
||||
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
|
||||
if (response == BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("Subscriptions AVAILABLE.");
|
||||
mSubscriptionsSupported = true;
|
||||
} else {
|
||||
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
|
||||
}
|
||||
|
||||
mSetupDone = true;
|
||||
} catch (RemoteException e) {
|
||||
if (listener != null) {
|
||||
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
|
||||
"RemoteException while setting up in-app billing."));
|
||||
}
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Intent serviceIntent = new Intent("ir.cafebazaar.pardakht.InAppBillingService.BIND");
|
||||
// serviceIntent.setPackage("com.farsitel.bazaar");
|
||||
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
|
||||
serviceIntent.setPackage("com.android.vending");
|
||||
if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
|
||||
// service available to handle that Intent
|
||||
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
|
||||
} else {
|
||||
// no service available to handle that Intent
|
||||
if (listener != null) {
|
||||
listener.onIabSetupFinished(
|
||||
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
|
||||
"Billing service unavailable on device."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of object, releasing resources. It's very important to call this
|
||||
* method when you are done with this object. It will release any resources
|
||||
* used by it such as service connections. Naturally, once the object is
|
||||
* disposed of, it can't be used again.
|
||||
*/
|
||||
public void dispose() {
|
||||
logDebug("Disposing.");
|
||||
mSetupDone = false;
|
||||
if (mServiceConn != null) {
|
||||
logDebug("Unbinding from service.");
|
||||
if (mContext != null) mContext.unbindService(mServiceConn);
|
||||
}
|
||||
mDisposed = true;
|
||||
mContext = null;
|
||||
mServiceConn = null;
|
||||
mService = null;
|
||||
mPurchaseListener = null;
|
||||
}
|
||||
|
||||
private void checkNotDisposed() {
|
||||
if (mDisposed)
|
||||
throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether subscriptions are supported.
|
||||
*/
|
||||
public boolean subscriptionsSupported() {
|
||||
checkNotDisposed();
|
||||
return mSubscriptionsSupported;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback that notifies when a purchase is finished.
|
||||
*/
|
||||
public interface OnIabPurchaseFinishedListener {
|
||||
/**
|
||||
* Called to notify that an in-app purchase finished. If the purchase was successful,
|
||||
* then the sku parameter specifies which item was purchased. If the purchase failed,
|
||||
* the sku and extraData parameters may or may not be null, depending on how far the purchase
|
||||
* process went.
|
||||
*
|
||||
* @param result The result of the purchase.
|
||||
* @param info The purchase information (null if purchase failed)
|
||||
*/
|
||||
void onIabPurchaseFinished(IabResult result, Purchase info);
|
||||
}
|
||||
|
||||
// The listener registered on launchPurchaseFlow, which we have to call back when
|
||||
// the purchase finishes
|
||||
OnIabPurchaseFinishedListener mPurchaseListener;
|
||||
|
||||
public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
|
||||
launchPurchaseFlow(act, sku, requestCode, listener, "");
|
||||
}
|
||||
|
||||
public void launchPurchaseFlow(Activity act, String sku, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener, String extraData) {
|
||||
launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
|
||||
}
|
||||
|
||||
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener) {
|
||||
launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
|
||||
}
|
||||
|
||||
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener, String extraData) {
|
||||
launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
|
||||
* which will involve bringing up the Google Play screen. The calling activity will be paused while
|
||||
* the user interacts with Google Play, and the result will be delivered via the activity's
|
||||
* {@link android.app.Activity#onActivityResult} method, at which point you must call
|
||||
* this object's {@link #handleActivityResult} method to continue the purchase flow. This method
|
||||
* MUST be called from the UI thread of the Activity.
|
||||
*
|
||||
* @param act The calling activity.
|
||||
* @param sku The sku of the item to purchase.
|
||||
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
|
||||
* @param requestCode A request code (to differentiate from other responses --
|
||||
* as in {@link android.app.Activity#startActivityForResult}).
|
||||
* @param listener The listener to notify when the purchase process finishes
|
||||
* @param extraData Extra data (developer payload), which will be returned with the purchase data
|
||||
* when the purchase completes. This extra data will be permanently bound to that purchase
|
||||
* and will always be returned when the purchase is queried.
|
||||
*/
|
||||
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
|
||||
OnIabPurchaseFinishedListener listener, String extraData) {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("launchPurchaseFlow");
|
||||
flagStartAsync("launchPurchaseFlow");
|
||||
IabResult result;
|
||||
|
||||
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
|
||||
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
|
||||
"Subscriptions are not available.");
|
||||
flagEndAsync();
|
||||
if (listener != null) listener.onIabPurchaseFinished(r, null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
|
||||
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
|
||||
int response = getResponseCodeFromBundle(buyIntentBundle);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
logError("Unable to buy item, Error response: " + getResponseDesc(response));
|
||||
flagEndAsync();
|
||||
result = new IabResult(response, "Unable to buy item");
|
||||
if (listener != null) listener.onIabPurchaseFinished(result, null);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
|
||||
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
|
||||
mRequestCode = requestCode;
|
||||
mPurchaseListener = listener;
|
||||
mPurchasingItemType = itemType;
|
||||
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
|
||||
requestCode, new Intent(),
|
||||
Integer.valueOf(0), Integer.valueOf(0),
|
||||
Integer.valueOf(0));
|
||||
} catch (SendIntentException e) {
|
||||
logError("SendIntentException while launching purchase flow for sku " + sku);
|
||||
e.printStackTrace();
|
||||
flagEndAsync();
|
||||
|
||||
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
|
||||
if (listener != null) listener.onIabPurchaseFinished(result, null);
|
||||
} catch (RemoteException e) {
|
||||
logError("RemoteException while launching purchase flow for sku " + sku);
|
||||
e.printStackTrace();
|
||||
flagEndAsync();
|
||||
|
||||
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
|
||||
if (listener != null) listener.onIabPurchaseFinished(result, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an activity result that's part of the purchase flow in in-app billing. If you
|
||||
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
|
||||
* Activity's {@link android.app.Activity@onActivityResult} method. This method
|
||||
* MUST be called from the UI thread of the Activity.
|
||||
*
|
||||
* @param requestCode The requestCode as you received it.
|
||||
* @param resultCode The resultCode as you received it.
|
||||
* @param data The data (Intent) as you received it.
|
||||
* @return Returns true if the result was related to a purchase flow and was handled;
|
||||
* false if the result was not related to a purchase, in which case you should
|
||||
* handle it normally.
|
||||
*/
|
||||
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
IabResult result;
|
||||
if (requestCode != mRequestCode) return false;
|
||||
|
||||
checkNotDisposed();
|
||||
checkSetupDone("handleActivityResult");
|
||||
|
||||
// end of async purchase operation that started on launchPurchaseFlow
|
||||
flagEndAsync();
|
||||
|
||||
if (data == null) {
|
||||
logError("Null data in IAB activity result.");
|
||||
result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
|
||||
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
int responseCode = getResponseCodeFromIntent(data);
|
||||
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
|
||||
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
|
||||
|
||||
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("Successful resultcode from purchase activity.");
|
||||
logDebug("Purchase data: " + purchaseData);
|
||||
logDebug("Data signature: " + dataSignature);
|
||||
logDebug("Extras: " + data.getExtras());
|
||||
logDebug("Expected item type: " + mPurchasingItemType);
|
||||
|
||||
if (purchaseData == null || dataSignature == null) {
|
||||
logError("BUG: either purchaseData or dataSignature is null.");
|
||||
logDebug("Extras: " + data.getExtras().toString());
|
||||
result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
|
||||
if (mPurchaseListener != null)
|
||||
mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
Purchase purchase = null;
|
||||
try {
|
||||
purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
|
||||
String sku = purchase.getSku();
|
||||
|
||||
// Verify signature
|
||||
if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
|
||||
logError("Purchase signature verification FAILED for sku " + sku);
|
||||
result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
|
||||
if (mPurchaseListener != null)
|
||||
mPurchaseListener.onIabPurchaseFinished(result, purchase);
|
||||
return true;
|
||||
}
|
||||
logDebug("Purchase signature successfully verified.");
|
||||
} catch (JSONException e) {
|
||||
logError("Failed to parse purchase data.");
|
||||
e.printStackTrace();
|
||||
result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
|
||||
if (mPurchaseListener != null)
|
||||
mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mPurchaseListener != null) {
|
||||
mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
|
||||
}
|
||||
} else if (resultCode == Activity.RESULT_OK) {
|
||||
// result code was OK, but in-app billing response was not OK.
|
||||
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
|
||||
if (mPurchaseListener != null) {
|
||||
result = new IabResult(responseCode, "Problem purchashing item.");
|
||||
mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
}
|
||||
} else if (resultCode == Activity.RESULT_CANCELED) {
|
||||
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
|
||||
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
|
||||
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
} else {
|
||||
logError("Purchase failed. Result code: " + Integer.toString(resultCode)
|
||||
+ ". Response: " + getResponseDesc(responseCode));
|
||||
result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
|
||||
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
|
||||
return queryInventory(querySkuDetails, moreSkus, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the inventory. This will query all owned items from the server, as well as
|
||||
* information on additional skus, if specified. This method may block or take long to execute.
|
||||
* Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
|
||||
*
|
||||
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
|
||||
* as purchase information.
|
||||
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
|
||||
* Ignored if null or if querySkuDetails is false.
|
||||
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
|
||||
* Ignored if null or if querySkuDetails is false.
|
||||
* @throws IabException if a problem occurs while refreshing the inventory.
|
||||
*/
|
||||
public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus,
|
||||
List<String> moreSubsSkus) throws IabException {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("queryInventory");
|
||||
try {
|
||||
Inventory inv = new Inventory();
|
||||
int r = queryPurchases(inv, ITEM_TYPE_INAPP);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying owned items).");
|
||||
}
|
||||
|
||||
if (querySkuDetails) {
|
||||
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
|
||||
}
|
||||
}
|
||||
|
||||
// if subscriptions are supported, then also query for subscriptions
|
||||
if (mSubscriptionsSupported) {
|
||||
r = queryPurchases(inv, ITEM_TYPE_SUBS);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
|
||||
}
|
||||
|
||||
if (querySkuDetails) {
|
||||
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
|
||||
if (r != BILLING_RESPONSE_RESULT_OK) {
|
||||
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inv;
|
||||
} catch (RemoteException e) {
|
||||
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
|
||||
} catch (JSONException e) {
|
||||
throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener that notifies when an inventory query operation completes.
|
||||
*/
|
||||
public interface QueryInventoryFinishedListener {
|
||||
/**
|
||||
* Called to notify that an inventory query operation completed.
|
||||
*
|
||||
* @param result The result of the operation.
|
||||
* @param inv The inventory.
|
||||
*/
|
||||
void onQueryInventoryFinished(IabResult result, Inventory inv);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper for inventory query. This will perform an inventory
|
||||
* query as described in {@link #queryInventory}, but will do so asynchronously
|
||||
* and call back the specified listener upon completion. This method is safe to
|
||||
* call from a UI thread.
|
||||
*
|
||||
* @param querySkuDetails as in {@link #queryInventory}
|
||||
* @param moreSkus as in {@link #queryInventory}
|
||||
* @param listener The listener to notify when the refresh operation completes.
|
||||
*/
|
||||
public void queryInventoryAsync(final boolean querySkuDetails,
|
||||
final List<String> moreSkus,
|
||||
final QueryInventoryFinishedListener listener) {
|
||||
final Handler handler = new Handler();
|
||||
checkNotDisposed();
|
||||
checkSetupDone("queryInventory");
|
||||
flagStartAsync("refresh inventory");
|
||||
(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
|
||||
Inventory inv = null;
|
||||
try {
|
||||
inv = queryInventory(querySkuDetails, moreSkus);
|
||||
} catch (IabException ex) {
|
||||
result = ex.getResult();
|
||||
}
|
||||
|
||||
flagEndAsync();
|
||||
|
||||
final IabResult result_f = result;
|
||||
final Inventory inv_f = inv;
|
||||
if (!mDisposed && listener != null) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
listener.onQueryInventoryFinished(result_f, inv_f);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
|
||||
queryInventoryAsync(true, null, listener);
|
||||
}
|
||||
|
||||
public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
|
||||
queryInventoryAsync(querySkuDetails, null, listener);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Consumes a given in-app product. Consuming can only be done on an item
|
||||
* that's owned, and as a result of consumption, the user will no longer own it.
|
||||
* This method may block or take long to return. Do not call from the UI thread.
|
||||
* For that, see {@link #consumeAsync}.
|
||||
*
|
||||
* @param itemInfo The PurchaseInfo that represents the item to consume.
|
||||
* @throws IabException if there is a problem during consumption.
|
||||
*/
|
||||
void consume(Purchase itemInfo) throws IabException {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("consume");
|
||||
|
||||
if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
|
||||
throw new IabException(IABHELPER_INVALID_CONSUMPTION,
|
||||
"Items of type '" + itemInfo.mItemType + "' can't be consumed.");
|
||||
}
|
||||
|
||||
try {
|
||||
String token = itemInfo.getToken();
|
||||
String sku = itemInfo.getSku();
|
||||
if (token == null || token.equals("")) {
|
||||
logError("Can't consume " + sku + ". No token.");
|
||||
throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
|
||||
+ sku + " " + itemInfo);
|
||||
}
|
||||
|
||||
logDebug("Consuming sku: " + sku + ", token: " + token);
|
||||
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
|
||||
if (response == BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("Successfully consumed sku: " + sku);
|
||||
} else {
|
||||
logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
|
||||
throw new IabException(response, "Error consuming sku " + sku);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that notifies when a consumption operation finishes.
|
||||
*/
|
||||
public interface OnConsumeFinishedListener {
|
||||
/**
|
||||
* Called to notify that a consumption has finished.
|
||||
*
|
||||
* @param purchase The purchase that was (or was to be) consumed.
|
||||
* @param result The result of the consumption operation.
|
||||
*/
|
||||
void onConsumeFinished(Purchase purchase, IabResult result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback that notifies when a multi-item consumption operation finishes.
|
||||
*/
|
||||
public interface OnConsumeMultiFinishedListener {
|
||||
/**
|
||||
* Called to notify that a consumption of multiple items has finished.
|
||||
*
|
||||
* @param purchases The purchases that were (or were to be) consumed.
|
||||
* @param results The results of each consumption operation, corresponding to each
|
||||
* sku.
|
||||
*/
|
||||
void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronous wrapper to item consumption. Works like {@link #consume}, but
|
||||
* performs the consumption in the background and notifies completion through
|
||||
* the provided listener. This method is safe to call from a UI thread.
|
||||
*
|
||||
* @param purchase The purchase to be consumed.
|
||||
* @param listener The listener to notify when the consumption operation finishes.
|
||||
*/
|
||||
public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("consume");
|
||||
List<Purchase> purchases = new ArrayList<Purchase>();
|
||||
purchases.add(purchase);
|
||||
consumeAsyncInternal(purchases, listener, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link consumeAsync}, but for multiple items at once.
|
||||
*
|
||||
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
|
||||
* @param listener The listener to notify when the consumption operation finishes.
|
||||
*/
|
||||
public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
|
||||
checkNotDisposed();
|
||||
checkSetupDone("consume");
|
||||
consumeAsyncInternal(purchases, null, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description for the given response code.
|
||||
*
|
||||
* @param code The response code
|
||||
* @return A human-readable string explaining the result code.
|
||||
* It also includes the result code numerically.
|
||||
*/
|
||||
public static String getResponseDesc(int code) {
|
||||
String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
|
||||
"3:Billing Unavailable/4:Item unavailable/" +
|
||||
"5:Developer Error/6:Error/7:Item Already Owned/" +
|
||||
"8:Item not owned").split("/");
|
||||
String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
|
||||
"-1002:Bad response received/" +
|
||||
"-1003:Purchase signature verification failed/" +
|
||||
"-1004:Send intent failed/" +
|
||||
"-1005:User cancelled/" +
|
||||
"-1006:Unknown purchase response/" +
|
||||
"-1007:Missing token/" +
|
||||
"-1008:Unknown error/" +
|
||||
"-1009:Subscriptions not available/" +
|
||||
"-1010:Invalid consumption attempt").split("/");
|
||||
|
||||
if (code <= IABHELPER_ERROR_BASE) {
|
||||
int index = IABHELPER_ERROR_BASE - code;
|
||||
if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
|
||||
else return String.valueOf(code) + ":Unknown IAB Helper Error";
|
||||
} else if (code < 0 || code >= iab_msgs.length)
|
||||
return String.valueOf(code) + ":Unknown";
|
||||
else
|
||||
return iab_msgs[code];
|
||||
}
|
||||
|
||||
|
||||
// Checks that setup was done; if not, throws an exception.
|
||||
void checkSetupDone(String operation) {
|
||||
if (!mSetupDone) {
|
||||
logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
|
||||
throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround to bug where sometimes response codes come as Long instead of Integer
|
||||
int getResponseCodeFromBundle(Bundle b) {
|
||||
Object o = b.get(RESPONSE_CODE);
|
||||
if (o == null) {
|
||||
logDebug("Bundle with null response code, assuming OK (known issue)");
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
} else if (o instanceof Integer) return ((Integer) o).intValue();
|
||||
else if (o instanceof Long) return (int) ((Long) o).longValue();
|
||||
else {
|
||||
logError("Unexpected type for bundle response code.");
|
||||
logError(o.getClass().getName());
|
||||
throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround to bug where sometimes response codes come as Long instead of Integer
|
||||
int getResponseCodeFromIntent(Intent i) {
|
||||
Object o = i.getExtras().get(RESPONSE_CODE);
|
||||
if (o == null) {
|
||||
logError("Intent with no response code, assuming OK (known issue)");
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
} else if (o instanceof Integer) return ((Integer) o).intValue();
|
||||
else if (o instanceof Long) return (int) ((Long) o).longValue();
|
||||
else {
|
||||
logError("Unexpected type for intent response code.");
|
||||
logError(o.getClass().getName());
|
||||
throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
void flagStartAsync(String operation) {
|
||||
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
|
||||
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
|
||||
mAsyncOperation = operation;
|
||||
mAsyncInProgress = true;
|
||||
logDebug("Starting async operation: " + operation);
|
||||
}
|
||||
|
||||
void flagEndAsync() {
|
||||
logDebug("Ending async operation: " + mAsyncOperation);
|
||||
mAsyncOperation = "";
|
||||
mAsyncInProgress = false;
|
||||
}
|
||||
|
||||
|
||||
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
|
||||
// Query purchases
|
||||
logDebug("Querying owned items, item type: " + itemType);
|
||||
logDebug("Package name: " + mContext.getPackageName());
|
||||
boolean verificationFailed = false;
|
||||
String continueToken = null;
|
||||
|
||||
do {
|
||||
logDebug("Calling getPurchases with continuation token: " + continueToken);
|
||||
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
|
||||
itemType, continueToken);
|
||||
|
||||
int response = getResponseCodeFromBundle(ownedItems);
|
||||
logDebug("Owned items response: " + String.valueOf(response));
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("getPurchases() failed: " + getResponseDesc(response));
|
||||
return response;
|
||||
}
|
||||
if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
|
||||
|| !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
|
||||
|| !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
|
||||
logError("Bundle returned from getPurchases() doesn't contain required fields.");
|
||||
return IABHELPER_BAD_RESPONSE;
|
||||
}
|
||||
|
||||
ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
|
||||
RESPONSE_INAPP_ITEM_LIST);
|
||||
ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
|
||||
RESPONSE_INAPP_PURCHASE_DATA_LIST);
|
||||
ArrayList<String> signatureList = ownedItems.getStringArrayList(
|
||||
RESPONSE_INAPP_SIGNATURE_LIST);
|
||||
|
||||
for (int i = 0; i < purchaseDataList.size(); ++i) {
|
||||
String purchaseData = purchaseDataList.get(i);
|
||||
String signature = signatureList.get(i);
|
||||
String sku = ownedSkus.get(i);
|
||||
if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
|
||||
logDebug("Sku is owned: " + sku);
|
||||
Purchase purchase = new Purchase(itemType, purchaseData, signature);
|
||||
|
||||
if (TextUtils.isEmpty(purchase.getToken())) {
|
||||
logWarn("BUG: empty/null token!");
|
||||
logDebug("Purchase data: " + purchaseData);
|
||||
}
|
||||
|
||||
// Record ownership and token
|
||||
inv.addPurchase(purchase);
|
||||
} else {
|
||||
logWarn("Purchase signature verification **FAILED**. Not adding item.");
|
||||
logDebug(" Purchase data: " + purchaseData);
|
||||
logDebug(" Signature: " + signature);
|
||||
verificationFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
|
||||
logDebug("Continuation token: " + continueToken);
|
||||
} while (!TextUtils.isEmpty(continueToken));
|
||||
|
||||
return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
|
||||
}
|
||||
|
||||
int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
|
||||
throws RemoteException, JSONException {
|
||||
logDebug("Querying SKU details.");
|
||||
ArrayList<String> skuList = new ArrayList<String>();
|
||||
skuList.addAll(inv.getAllOwnedSkus(itemType));
|
||||
if (moreSkus != null) {
|
||||
for (String sku : moreSkus) {
|
||||
if (!skuList.contains(sku)) {
|
||||
skuList.add(sku);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skuList.size() == 0) {
|
||||
logDebug("queryPrices: nothing to do because there are no SKUs.");
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
}
|
||||
|
||||
Bundle querySkus = new Bundle();
|
||||
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
|
||||
Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
|
||||
itemType, querySkus);
|
||||
|
||||
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
|
||||
int response = getResponseCodeFromBundle(skuDetails);
|
||||
if (response != BILLING_RESPONSE_RESULT_OK) {
|
||||
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
|
||||
return response;
|
||||
} else {
|
||||
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
|
||||
return IABHELPER_BAD_RESPONSE;
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<String> responseList = skuDetails.getStringArrayList(
|
||||
RESPONSE_GET_SKU_DETAILS_LIST);
|
||||
|
||||
for (String thisResponse : responseList) {
|
||||
SkuDetails d = new SkuDetails(itemType, thisResponse);
|
||||
logDebug("Got sku details: " + d);
|
||||
inv.addSkuDetails(d);
|
||||
}
|
||||
return BILLING_RESPONSE_RESULT_OK;
|
||||
}
|
||||
|
||||
|
||||
void consumeAsyncInternal(final List<Purchase> purchases,
|
||||
final OnConsumeFinishedListener singleListener,
|
||||
final OnConsumeMultiFinishedListener multiListener) {
|
||||
final Handler handler = new Handler();
|
||||
flagStartAsync("consume");
|
||||
(new Thread(new Runnable() {
|
||||
public void run() {
|
||||
final List<IabResult> results = new ArrayList<IabResult>();
|
||||
for (Purchase purchase : purchases) {
|
||||
try {
|
||||
consume(purchase);
|
||||
results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
|
||||
} catch (IabException ex) {
|
||||
results.add(ex.getResult());
|
||||
}
|
||||
}
|
||||
|
||||
flagEndAsync();
|
||||
if (!mDisposed && singleListener != null) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
singleListener.onConsumeFinished(purchases.get(0), results.get(0));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!mDisposed && multiListener != null) {
|
||||
handler.post(new Runnable() {
|
||||
public void run() {
|
||||
multiListener.onConsumeMultiFinished(purchases, results);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
void logDebug(String msg) {
|
||||
if (mDebugLog) Log.d(mDebugTag, msg);
|
||||
}
|
||||
|
||||
void logError(String msg) {
|
||||
Log.e(mDebugTag, "In-app billing error: " + msg);
|
||||
}
|
||||
|
||||
void logWarn(String msg) {
|
||||
Log.w(mDebugTag, "In-app billing warning: " + msg);
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
/**
|
||||
* Represents the result of an in-app billing operation.
|
||||
* A result is composed of a response code (an integer) and possibly a
|
||||
* message (String). You can get those by calling
|
||||
* {@link #getResponse} and {@link #getMessage()}, respectively. You
|
||||
* can also inquire whether a result is a success or a failure by
|
||||
* calling {@link #isSuccess()} and {@link #isFailure()}.
|
||||
*/
|
||||
public class IabResult {
|
||||
int mResponse;
|
||||
String mMessage;
|
||||
|
||||
public IabResult(int response, String message) {
|
||||
mResponse = response;
|
||||
if (message == null || message.trim().length() == 0) {
|
||||
mMessage = IabHelper.getResponseDesc(response);
|
||||
}
|
||||
else {
|
||||
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
|
||||
}
|
||||
}
|
||||
public int getResponse() { return mResponse; }
|
||||
public String getMessage() { return mMessage; }
|
||||
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
|
||||
public boolean isFailure() { return !isSuccess(); }
|
||||
public String toString() { return "IabResult: " + getMessage(); }
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents a block of information about in-app items.
|
||||
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
|
||||
*/
|
||||
public class Inventory {
|
||||
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
|
||||
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
|
||||
|
||||
Inventory() { }
|
||||
|
||||
/** Returns the listing details for an in-app product. */
|
||||
public SkuDetails getSkuDetails(String sku) {
|
||||
return mSkuMap.get(sku);
|
||||
}
|
||||
|
||||
/** Returns purchase information for a given product, or null if there is no purchase. */
|
||||
public Purchase getPurchase(String sku) {
|
||||
return mPurchaseMap.get(sku);
|
||||
}
|
||||
|
||||
/** Returns whether or not there exists a purchase of the given product. */
|
||||
public boolean hasPurchase(String sku) {
|
||||
return mPurchaseMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/** Return whether or not details about the given product are available. */
|
||||
public boolean hasDetails(String sku) {
|
||||
return mSkuMap.containsKey(sku);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase a purchase (locally) from the inventory, given its product ID. This just
|
||||
* modifies the Inventory object locally and has no effect on the server! This is
|
||||
* useful when you have an existing Inventory object which you know to be up to date,
|
||||
* and you have just consumed an item successfully, which means that erasing its
|
||||
* purchase data from the Inventory you already have is quicker than querying for
|
||||
* a new Inventory.
|
||||
*/
|
||||
public void erasePurchase(String sku) {
|
||||
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
|
||||
}
|
||||
|
||||
/** Returns a list of all owned product IDs. */
|
||||
List<String> getAllOwnedSkus() {
|
||||
return new ArrayList<String>(mPurchaseMap.keySet());
|
||||
}
|
||||
|
||||
/** Returns a list of all owned product IDs of a given type */
|
||||
List<String> getAllOwnedSkus(String itemType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Purchase p : mPurchaseMap.values()) {
|
||||
if (p.getItemType().equals(itemType)) result.add(p.getSku());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Returns a list of all purchases. */
|
||||
List<Purchase> getAllPurchases() {
|
||||
return new ArrayList<Purchase>(mPurchaseMap.values());
|
||||
}
|
||||
|
||||
void addSkuDetails(SkuDetails d) {
|
||||
mSkuMap.put(d.getSku(), d);
|
||||
}
|
||||
|
||||
void addPurchase(Purchase p) {
|
||||
mPurchaseMap.put(p.getSku(), p);
|
||||
}
|
||||
}
|
||||
@@ -1,540 +0,0 @@
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Reference to http://blog.csdn.net/way_ping_li/article/details/8487866
|
||||
* and improved some features...
|
||||
*/
|
||||
public class LogRecorder {
|
||||
|
||||
public static final int LOG_LEVEL_NO_SET = 0;
|
||||
|
||||
public static final int LOG_BUFFER_MAIN = 1;
|
||||
public static final int LOG_BUFFER_SYSTEM = 1 << 1;
|
||||
public static final int LOG_BUFFER_RADIO = 1 << 2;
|
||||
public static final int LOG_BUFFER_EVENTS = 1 << 3;
|
||||
public static final int LOG_BUFFER_KERNEL = 1 << 4; // not be supported by now
|
||||
|
||||
public static final int LOG_BUFFER_DEFAULT = LOG_BUFFER_MAIN | LOG_BUFFER_SYSTEM;
|
||||
|
||||
public static final int INVALID_PID = -1;
|
||||
|
||||
public String mFileSuffix;
|
||||
public String mFolderPath;
|
||||
public int mFileSizeLimitation;
|
||||
public int mLevel;
|
||||
public List<String> mFilterTags = new ArrayList<>();
|
||||
public int mPID = INVALID_PID;
|
||||
|
||||
public boolean mUseLogcatFileOut = false;
|
||||
|
||||
private LogDumper mLogDumper = null;
|
||||
|
||||
public static final int EVENT_RESTART_LOG = 1001;
|
||||
|
||||
private RestartHandler mHandler;
|
||||
|
||||
private static class RestartHandler extends Handler {
|
||||
final LogRecorder logRecorder;
|
||||
public RestartHandler(LogRecorder logRecorder) {
|
||||
this.logRecorder = logRecorder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
if (msg.what == EVENT_RESTART_LOG) {
|
||||
logRecorder.stop();
|
||||
logRecorder.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LogRecorder() {
|
||||
mHandler = new RestartHandler(this);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
// make sure the out folder exist
|
||||
// TODO support multi-phase path
|
||||
File file = new File(mFolderPath);
|
||||
if (!file.exists()) {
|
||||
file.mkdirs();
|
||||
}
|
||||
|
||||
String cmdStr = collectLogcatCommand();
|
||||
|
||||
if (mLogDumper != null) {
|
||||
mLogDumper.stopDumping();
|
||||
mLogDumper = null;
|
||||
}
|
||||
|
||||
mLogDumper = new LogDumper(mFolderPath, mFileSuffix, mFileSizeLimitation, cmdStr, mHandler);
|
||||
mLogDumper.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// TODO maybe should clean the log buffer first?
|
||||
if (mLogDumper != null) {
|
||||
mLogDumper.stopDumping();
|
||||
mLogDumper = null;
|
||||
}
|
||||
}
|
||||
|
||||
private String collectLogcatCommand() {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
final String SPACE = " ";
|
||||
stringBuilder.append("logcat");
|
||||
|
||||
// TODO select ring buffer, -b
|
||||
|
||||
// TODO set out format
|
||||
stringBuilder.append(SPACE);
|
||||
stringBuilder.append("-v time");
|
||||
|
||||
// append tag filters
|
||||
String levelStr = getLevelStr();
|
||||
|
||||
if (!mFilterTags.isEmpty()) {
|
||||
stringBuilder.append(SPACE);
|
||||
stringBuilder.append("-s");
|
||||
for (int i = 0; i < mFilterTags.size(); i++) {
|
||||
String tag = mFilterTags.get(i) + ":" + levelStr;
|
||||
stringBuilder.append(SPACE);
|
||||
stringBuilder.append(tag);
|
||||
}
|
||||
} else {
|
||||
if (!TextUtils.isEmpty(levelStr)) {
|
||||
stringBuilder.append(SPACE);
|
||||
stringBuilder.append("*:" + levelStr);
|
||||
}
|
||||
}
|
||||
|
||||
// logcat -f , but the rotated count default is 4?
|
||||
// can`t be sure to use that feature
|
||||
if (mPID != INVALID_PID) {
|
||||
mUseLogcatFileOut = false;
|
||||
String pidStr = adjustPIDStr();
|
||||
if (!TextUtils.isEmpty(pidStr)) {
|
||||
stringBuilder.append(SPACE);
|
||||
stringBuilder.append("|");
|
||||
stringBuilder.append(SPACE);
|
||||
stringBuilder.append("grep (" + pidStr + ")");
|
||||
}
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private String getLevelStr() {
|
||||
switch (mLevel) {
|
||||
case 2:
|
||||
return "V";
|
||||
case 3:
|
||||
return "D";
|
||||
case 4:
|
||||
return "I";
|
||||
case 5:
|
||||
return "W";
|
||||
case 6:
|
||||
return "E";
|
||||
case 7:
|
||||
return "F";
|
||||
}
|
||||
|
||||
return "V";
|
||||
}
|
||||
|
||||
/**
|
||||
* Android`s user app pid is bigger than 1000.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String adjustPIDStr() {
|
||||
if (mPID == INVALID_PID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String pidStr = String.valueOf(mPID);
|
||||
int length = pidStr.length();
|
||||
if (length < 4) {
|
||||
pidStr = " 0" + pidStr;
|
||||
}
|
||||
|
||||
if (length == 4) {
|
||||
pidStr = " " + pidStr;
|
||||
}
|
||||
|
||||
return pidStr;
|
||||
}
|
||||
|
||||
|
||||
private class LogDumper extends Thread {
|
||||
final String logPath;
|
||||
final String logFileSuffix;
|
||||
final int logFileLimitation;
|
||||
final String logCmd;
|
||||
|
||||
final RestartHandler restartHandler;
|
||||
|
||||
private Process logcatProc;
|
||||
private BufferedReader mReader = null;
|
||||
private FileOutputStream out = null;
|
||||
|
||||
private boolean mRunning = true;
|
||||
final private Object mRunningLock = new Object();
|
||||
|
||||
private long currentFileSize;
|
||||
|
||||
public LogDumper(String folderPath, String suffix,
|
||||
int fileSizeLimitation, String command,
|
||||
RestartHandler handler) {
|
||||
logPath = folderPath;
|
||||
logFileSuffix = suffix;
|
||||
logFileLimitation = fileSizeLimitation;
|
||||
logCmd = command;
|
||||
restartHandler = handler;
|
||||
|
||||
String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
|
||||
.format(new Date(System.currentTimeMillis()));
|
||||
String fileName = (TextUtils.isEmpty(logFileSuffix)) ? date : (logFileSuffix + "-"+ date);
|
||||
try {
|
||||
out = new FileOutputStream(new File(logPath, fileName + ".log"));
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stopDumping() {
|
||||
synchronized (mRunningLock) {
|
||||
mRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
logcatProc = Runtime.getRuntime().exec(logCmd);
|
||||
mReader = new BufferedReader(new InputStreamReader(
|
||||
logcatProc.getInputStream()), 1024);
|
||||
String line = null;
|
||||
while (mRunning && (line = mReader.readLine()) != null) {
|
||||
if (!mRunning) {
|
||||
break;
|
||||
}
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (out != null && !line.isEmpty()) {
|
||||
byte[] data = (line + "\n").getBytes();
|
||||
out.write(data);
|
||||
if (logFileLimitation != 0) {
|
||||
currentFileSize += data.length;
|
||||
if (currentFileSize > logFileLimitation*1024) {
|
||||
restartHandler.sendEmptyMessage(EVENT_RESTART_LOG);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
if (logcatProc != null) {
|
||||
logcatProc.destroy();
|
||||
logcatProc = null;
|
||||
}
|
||||
if (mReader != null) {
|
||||
try {
|
||||
mReader.close();
|
||||
mReader = null;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
out = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
/**
|
||||
* context object
|
||||
*/
|
||||
private Context mContext;
|
||||
|
||||
/**
|
||||
* the folder name that we save log files to,
|
||||
* just folder name, not the whole path,
|
||||
* if set this, will save log files to /sdcard/$mLogFolderName folder,
|
||||
* use /sdcard/$ApplicationName as default.
|
||||
*/
|
||||
private String mLogFolderName;
|
||||
|
||||
/**
|
||||
* the whole folder path that we save log files to,
|
||||
* this setting`s priority is bigger than folder name.
|
||||
*/
|
||||
private String mLogFolderPath;
|
||||
|
||||
/**
|
||||
* the log file suffix,
|
||||
* if this is sot, it will be appended to log file name automatically
|
||||
*/
|
||||
private String mLogFileNameSuffix = "";
|
||||
|
||||
/**
|
||||
* single log file size limitation,
|
||||
* in k-bytes, ex. set to 16, is 16KB limitation.
|
||||
*/
|
||||
private int mLogFileSizeLimitation = 0;
|
||||
|
||||
/**
|
||||
* log level, see android.util.Log, 2 - 7,
|
||||
* if not be set, will use verbose as default
|
||||
*/
|
||||
private int mLogLevel = LogRecorder.LOG_LEVEL_NO_SET;
|
||||
|
||||
/**
|
||||
* can set several filter tags
|
||||
* logcat -s ActivityManager:V SystemUI:V
|
||||
*/
|
||||
private List<String> mLogFilterTags = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* filter through pid, by setting this with your APP PID,
|
||||
* the log recorder will just record the APP`s own log,
|
||||
* use one call: android.os.Process.myPid().
|
||||
*/
|
||||
private int mPID = LogRecorder.INVALID_PID;
|
||||
|
||||
/**
|
||||
* which log buffer to catch...
|
||||
* <p/>
|
||||
* Request alternate ring buffer, 'main', 'system', 'radio'
|
||||
* or 'events'. Multiple -b parameters are allowed and the
|
||||
* results are interleaved.
|
||||
* <p/>
|
||||
* The default is -b main -b system.
|
||||
*/
|
||||
private int mLogBuffersSelected = LogRecorder.LOG_BUFFER_DEFAULT;
|
||||
|
||||
/**
|
||||
* log output format, don`t support config yet, use $time format as default.
|
||||
* <p/>
|
||||
* Log messages contain a number of metadata fields, in addition to the tag and priority.
|
||||
* You can modify the output format for messages so that they display a specific metadata
|
||||
* field. To do so, you use the -v option and specify one of the supported output formats
|
||||
* listed below.
|
||||
* <p/>
|
||||
* brief — Display priority/tag and PID of the process issuing the message.
|
||||
* process — Display PID only.
|
||||
* tag — Display the priority/tag only.
|
||||
* thread - Display the priority, tag, and the PID(process ID) and TID(thread ID)
|
||||
* of the thread issuing the message.
|
||||
* raw — Display the raw log message, with no other metadata fields.
|
||||
* time — Display the date, invocation time, priority/tag, and PID of
|
||||
* the process issuing the message.
|
||||
* threadtime — Display the date, invocation time, priority, tag, and the PID(process ID)
|
||||
* and TID(thread ID) of the thread issuing the message.
|
||||
* long — Display all metadata fields and separate messages with blank lines.
|
||||
*/
|
||||
private int mLogOutFormat;
|
||||
|
||||
/**
|
||||
* set log out folder name
|
||||
*
|
||||
* @param logFolderName folder name
|
||||
* @return The same Builder.
|
||||
*/
|
||||
public Builder setLogFolderName(String logFolderName) {
|
||||
this.mLogFolderName = logFolderName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set log out folder path
|
||||
*
|
||||
* @param logFolderPath out folder absolute path
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setLogFolderPath(String logFolderPath) {
|
||||
this.mLogFolderPath = logFolderPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set log file name suffix
|
||||
*
|
||||
* @param logFileNameSuffix auto appened suffix
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setLogFileNameSuffix(String logFileNameSuffix) {
|
||||
this.mLogFileNameSuffix = logFileNameSuffix;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the file size limitation
|
||||
*
|
||||
* @param fileSizeLimitation file size limitation in KB
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setLogFileSizeLimitation(int fileSizeLimitation) {
|
||||
this.mLogFileSizeLimitation = fileSizeLimitation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the log level
|
||||
*
|
||||
* @param logLevel log level, 2-7
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setLogLevel(int logLevel) {
|
||||
this.mLogLevel = logLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* add log filterspec tag name, can add multiple ones,
|
||||
* they use the same log level set by setLogLevel()
|
||||
*
|
||||
* @param tag tag name
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder addLogFilterTag(String tag) {
|
||||
mLogFilterTags.add(tag);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* which process`s log
|
||||
*
|
||||
* @param mPID process id
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setPID(int mPID) {
|
||||
this.mPID = mPID;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* -b radio, -b main, -b system, -b events
|
||||
* -b main -b system as default
|
||||
*
|
||||
* @param logBuffersSelected one of
|
||||
* LOG_BUFFER_MAIN = 1 << 0;
|
||||
* LOG_BUFFER_SYSTEM = 1 << 1;
|
||||
* LOG_BUFFER_RADIO = 1 << 2;
|
||||
* LOG_BUFFER_EVENTS = 1 << 3;
|
||||
* LOG_BUFFER_KERNEL = 1 << 4;
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setLogBufferSelected(int logBuffersSelected) {
|
||||
this.mLogBuffersSelected = logBuffersSelected;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets log out format, -v parameter
|
||||
*
|
||||
* @param logOutFormat out format, like -v time
|
||||
* @return the same Builder
|
||||
*/
|
||||
public Builder setLogOutFormat(int logOutFormat) {
|
||||
this.mLogOutFormat = mLogOutFormat;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* call this only if mLogFolderName and mLogFolderPath not
|
||||
* be set both.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private void applyAppNameAsOutfolderName() {
|
||||
try {
|
||||
String appName = mContext.getPackageName();
|
||||
String versionName = mContext.getPackageManager().getPackageInfo(
|
||||
appName, 0).versionName;
|
||||
int versionCode = mContext.getPackageManager()
|
||||
.getPackageInfo(appName, 0).versionCode;
|
||||
mLogFolderName = appName + "-" + versionName + "-" + versionCode;
|
||||
mLogFolderPath = applyOutfolderPath();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
private String applyOutfolderPath() {
|
||||
String outPath = "";
|
||||
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
|
||||
outPath = Environment.getExternalStorageDirectory()
|
||||
.getAbsolutePath() + File.separator + mLogFolderName;
|
||||
}
|
||||
|
||||
return outPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine all of the options that have been set and return
|
||||
* a new {@link LogRecorder} object.
|
||||
*/
|
||||
public LogRecorder build() {
|
||||
LogRecorder logRecorder = new LogRecorder();
|
||||
|
||||
// no folder name & folder path be set
|
||||
if (TextUtils.isEmpty(mLogFolderName)
|
||||
&& TextUtils.isEmpty(mLogFolderPath)) {
|
||||
applyAppNameAsOutfolderName();
|
||||
}
|
||||
|
||||
// make sure out path be set
|
||||
if (TextUtils.isEmpty(mLogFolderPath)) {
|
||||
mLogFolderPath = applyOutfolderPath();
|
||||
}
|
||||
|
||||
logRecorder.mFolderPath = mLogFolderPath;
|
||||
logRecorder.mFileSuffix = mLogFileNameSuffix;
|
||||
logRecorder.mFileSizeLimitation = mLogFileSizeLimitation;
|
||||
logRecorder.mLevel = mLogLevel;
|
||||
if (!mLogFilterTags.isEmpty()) {
|
||||
for (int i = 0; i < mLogFilterTags.size(); i++) {
|
||||
logRecorder.mFilterTags.add(mLogFilterTags.get(i));
|
||||
}
|
||||
}
|
||||
logRecorder.mPID = mPID;
|
||||
|
||||
return logRecorder;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app billing purchase.
|
||||
*/
|
||||
public class Purchase {
|
||||
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
|
||||
String mOrderId;
|
||||
String mPackageName;
|
||||
String mSku;
|
||||
long mPurchaseTime;
|
||||
int mPurchaseState;
|
||||
String mDeveloperPayload;
|
||||
String mToken;
|
||||
String mOriginalJson;
|
||||
String mSignature;
|
||||
|
||||
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mOriginalJson = jsonPurchaseInfo;
|
||||
JSONObject o = new JSONObject(mOriginalJson);
|
||||
mOrderId = o.optString("orderId");
|
||||
mPackageName = o.optString("packageName");
|
||||
mSku = o.optString("productId");
|
||||
mPurchaseTime = o.optLong("purchaseTime");
|
||||
mPurchaseState = o.optInt("purchaseState");
|
||||
mDeveloperPayload = o.optString("developerPayload");
|
||||
mToken = o.optString("token", o.optString("purchaseToken"));
|
||||
mSignature = signature;
|
||||
}
|
||||
|
||||
public String getItemType() { return mItemType; }
|
||||
public String getOrderId() { return mOrderId; }
|
||||
public String getPackageName() { return mPackageName; }
|
||||
public String getSku() { return mSku; }
|
||||
public long getPurchaseTime() { return mPurchaseTime; }
|
||||
public int getPurchaseState() { return mPurchaseState; }
|
||||
public String getDeveloperPayload() { return mDeveloperPayload; }
|
||||
public String getToken() { return mToken; }
|
||||
public String getOriginalJson() { return mOriginalJson; }
|
||||
public String getSignature() { return mSignature; }
|
||||
|
||||
@Override
|
||||
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
/**
|
||||
* Security-related methods. For a secure implementation, all of this code
|
||||
* should be implemented on a server that communicates with the
|
||||
* application on the device. For the sake of simplicity and clarity of this
|
||||
* example, this code is included here and is executed on the device. If you
|
||||
* must verify the purchases on the phone, you should obfuscate this code to
|
||||
* make it harder for an attacker to replace the code with stubs that treat all
|
||||
* purchases as verified.
|
||||
*/
|
||||
public class Security {
|
||||
private static final String TAG = "IABUtil/Security";
|
||||
|
||||
private static final String KEY_FACTORY_ALGORITHM = "RSA";
|
||||
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
|
||||
|
||||
/**
|
||||
* Verifies that the data was signed with the given signature, and returns
|
||||
* the verified purchase. The data is in JSON format and signed
|
||||
* with a private key. The data also contains the {@link PurchaseState}
|
||||
* and product ID of the purchase.
|
||||
* @param base64PublicKey the base64-encoded public key to use for verifying.
|
||||
* @param signedData the signed JSON string (signed, not encrypted)
|
||||
* @param signature the signature for the data, signed with the private key
|
||||
*/
|
||||
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
|
||||
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
|
||||
TextUtils.isEmpty(signature)) {
|
||||
Log.e(TAG, "Purchase verification failed: missing data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
PublicKey key = Security.generatePublicKey(base64PublicKey);
|
||||
return Security.verify(key, signedData, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a PublicKey instance from a string containing the
|
||||
* Base64-encoded public key.
|
||||
*
|
||||
* @param encodedPublicKey Base64-encoded public key
|
||||
* @throws IllegalArgumentException if encodedPublicKey is invalid
|
||||
*/
|
||||
public static PublicKey generatePublicKey(String encodedPublicKey) {
|
||||
try {
|
||||
byte[] decodedKey = Base64.decode(encodedPublicKey);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
|
||||
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
throw new IllegalArgumentException(e);
|
||||
} catch (Base64DecoderException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the signature from the server matches the computed
|
||||
* signature on the data. Returns true if the data is correctly signed.
|
||||
*
|
||||
* @param publicKey public key associated with the developer account
|
||||
* @param signedData signed data from server
|
||||
* @param signature server signature
|
||||
* @return true if the data and signature match
|
||||
*/
|
||||
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
|
||||
Signature sig;
|
||||
try {
|
||||
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(signedData.getBytes());
|
||||
if (!sig.verify(Base64.decode(signature))) {
|
||||
Log.e(TAG, "Signature verification failed.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "NoSuchAlgorithmException.");
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.e(TAG, "Invalid key specification.");
|
||||
} catch (SignatureException e) {
|
||||
Log.e(TAG, "Signature exception.");
|
||||
} catch (Base64DecoderException e) {
|
||||
Log.e(TAG, "Base64 decoding failed.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/* Copyright (c) 2012 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.v2ray.ang.util;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an in-app product's listing details.
|
||||
*/
|
||||
public class SkuDetails {
|
||||
String mItemType;
|
||||
String mSku;
|
||||
String mType;
|
||||
String mPrice;
|
||||
String mTitle;
|
||||
String mDescription;
|
||||
String mJson;
|
||||
|
||||
public SkuDetails(String jsonSkuDetails) throws JSONException {
|
||||
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
|
||||
}
|
||||
|
||||
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
|
||||
mItemType = itemType;
|
||||
mJson = jsonSkuDetails;
|
||||
JSONObject o = new JSONObject(mJson);
|
||||
mSku = o.optString("productId");
|
||||
mType = o.optString("type");
|
||||
mPrice = o.optString("price");
|
||||
mTitle = o.optString("title");
|
||||
mDescription = o.optString("description");
|
||||
}
|
||||
|
||||
public String getSku() { return mSku; }
|
||||
public String getType() { return mType; }
|
||||
public String getPrice() { return mPrice; }
|
||||
public String getTitle() { return mTitle; }
|
||||
public String getDescription() { return mDescription; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SkuDetails:" + mJson;
|
||||
}
|
||||
}
|
||||
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
Normal file → Executable file
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
Normal file → Executable file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
Normal file → Executable file
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
Normal file → Executable file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
Normal file → Executable file
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
Normal file → Executable file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
Normal file → Executable file
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
Normal file → Executable file
Binary file not shown.
@@ -1,10 +1,8 @@
|
||||
package com.v2ray.ang
|
||||
|
||||
//import com.squareup.leakcanary.LeakCanary
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.jetbrains.anko.defaultSharedPreferences
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
class AngApplication : MultiDexApplication() {
|
||||
companion object {
|
||||
@@ -15,18 +13,17 @@ class AngApplication : MultiDexApplication() {
|
||||
var firstRun = false
|
||||
private set
|
||||
|
||||
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// LeakCanary.install(this)
|
||||
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
|
||||
if (firstRun)
|
||||
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
|
||||
|
||||
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
|
||||
AngConfigManager.inject(this)
|
||||
MMKV.initialize(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,46 @@ package com.v2ray.ang
|
||||
*/
|
||||
object AppConfig {
|
||||
const val ANG_PACKAGE = "com.v2ray.ang"
|
||||
const val ANG_CONFIG = "ang_config"
|
||||
const val PREF_CURR_CONFIG = "pref_v2ray_config"
|
||||
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid"
|
||||
const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name"
|
||||
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
|
||||
const val PREF_CURR_CONFIG_OUTBOUND_TAGS = "pref_v2ray_config_outbound_tags"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val PREF_MODE = "pref_mode"
|
||||
|
||||
const val VMESS_PROTOCOL: String = "vmess://"
|
||||
const val SS_PROTOCOL: String = "ss://"
|
||||
const val SOCKS_PROTOCOL: String = "socks://"
|
||||
// legacy
|
||||
const val ANG_CONFIG = "ang_config"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||
|
||||
// Preferences mapped to MMKV
|
||||
const val PREF_MODE = "pref_mode"
|
||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
||||
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
|
||||
const val PREF_VPN_DNS = "pref_vpn_dns"
|
||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
||||
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
||||
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
|
||||
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
|
||||
// const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
// const val PREF_HTTP_PORT = "pref_http_port"
|
||||
// const val PREF_DONATE = "pref_donate"
|
||||
// const val PREF_LICENSES = "pref_licenses"
|
||||
// const val PREF_FEEDBACK = "pref_feedback"
|
||||
// const val PREF_TG_GROUP = "pref_tg_group"
|
||||
// const val PREF_AUTO_RESTART = "pref_auto_restart"
|
||||
|
||||
const val HTTP_PROTOCOL: String = "http://"
|
||||
const val HTTPS_PROTOCOL: String = "https://"
|
||||
|
||||
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
|
||||
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
|
||||
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
|
||||
@@ -28,9 +56,6 @@ object AppConfig {
|
||||
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
|
||||
const val TASKER_DEFAULT_GUID = "Default"
|
||||
|
||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
|
||||
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
|
||||
const val TAG_AGENT = "proxy"
|
||||
const val TAG_DIRECT = "direct"
|
||||
const val TAG_BLOCKED = "block"
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
enum class EConfigType(val value: Int) {
|
||||
VMESS(1),
|
||||
CUSTOM(2),
|
||||
SHADOWSOCKS(3),
|
||||
SOCKS(4);
|
||||
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.lowercase(),
|
||||
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.lowercase(),
|
||||
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,159 +1,446 @@
|
||||
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(
|
||||
val stats: Any?=null,
|
||||
var stats: Any? = null,
|
||||
val log: LogBean,
|
||||
val policy: PolicyBean,
|
||||
var policy: PolicyBean?,
|
||||
val inbounds: ArrayList<InboundBean>,
|
||||
var outbounds: ArrayList<OutboundBean>,
|
||||
var dns: DnsBean,
|
||||
val routing: RoutingBean) {
|
||||
val routing: RoutingBean,
|
||||
val api: Any? = null,
|
||||
val transport: Any? = null,
|
||||
val reverse: Any? = null,
|
||||
var fakedns: Any? = 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,
|
||||
val error: String,
|
||||
val loglevel: String)
|
||||
var loglevel: String?,
|
||||
val dnsLog: Boolean? = null)
|
||||
|
||||
data class InboundBean(
|
||||
var tag: String,
|
||||
var port: Int,
|
||||
var protocol: String,
|
||||
var listen: String?=null,
|
||||
val settings: InSettingsBean,
|
||||
val sniffing: SniffingBean?) {
|
||||
var listen: String? = null,
|
||||
val settings: Any? = null,
|
||||
val sniffing: SniffingBean?,
|
||||
val streamSettings: Any? = null,
|
||||
val allocate: Any? = null) {
|
||||
|
||||
data class InSettingsBean(val auth: String? = null,
|
||||
val udp: Boolean? = null,
|
||||
val userLevel: Int? =null,
|
||||
val userLevel: Int? = null,
|
||||
val address: String? = null,
|
||||
val port: Int? = null,
|
||||
val network: String? = null)
|
||||
|
||||
data class SniffingBean(var enabled: Boolean,
|
||||
val destOverride: 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 settings: OutSettingsBean?,
|
||||
var streamSettings: StreamSettingsBean?,
|
||||
var mux: MuxBean?) {
|
||||
var settings: OutSettingsBean? = null,
|
||||
var streamSettings: StreamSettingsBean? = null,
|
||||
val proxySettings: Any? = null,
|
||||
val sendThrough: String? = null,
|
||||
val mux: MuxBean? = MuxBean(false)) {
|
||||
|
||||
data class OutSettingsBean(var vnext: List<VnextBean>?,
|
||||
var servers: List<ServersBean>?,
|
||||
var response: Response) {
|
||||
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
|
||||
var servers: List<ServersBean>? = null,
|
||||
/*Blackhole*/
|
||||
var response: Response? = null,
|
||||
/*DNS*/
|
||||
val network: String? = null,
|
||||
val address: String? = null,
|
||||
val port: Int? = null,
|
||||
/*Freedom*/
|
||||
var domainStrategy: String? = null,
|
||||
val redirect: String? = null,
|
||||
val userLevel: Int? = null,
|
||||
/*Loopback*/
|
||||
val inboundTag: String? = null) {
|
||||
|
||||
data class VnextBean(var address: String,
|
||||
var port: Int,
|
||||
data class VnextBean(var address: String = "",
|
||||
var port: Int = DEFAULT_PORT,
|
||||
var users: List<UsersBean>) {
|
||||
|
||||
data class UsersBean(var id: String,
|
||||
var alterId: Int,
|
||||
var security: String,
|
||||
var level: Int)
|
||||
data class UsersBean(var id: String = "",
|
||||
var security: String = DEFAULT_SECURITY,
|
||||
var level: Int = DEFAULT_LEVEL,
|
||||
var encryption: String = "",
|
||||
var flow: String = "")
|
||||
}
|
||||
|
||||
data class ServersBean(var address: String,
|
||||
var method: String,
|
||||
var ota: Boolean,
|
||||
var password: String,
|
||||
var port: Int,
|
||||
var level: Int)
|
||||
data class ServersBean(var address: String = "",
|
||||
var method: String = "chacha20-poly1305",
|
||||
var ota: Boolean = false,
|
||||
var password: String = "",
|
||||
var port: Int = DEFAULT_PORT,
|
||||
var level: Int = DEFAULT_LEVEL,
|
||||
val email: String? = null,
|
||||
val flow: String? = null,
|
||||
val ivCheck: Boolean? = null,
|
||||
var users: List<SocksUsersBean>? = null) {
|
||||
|
||||
|
||||
data class SocksUsersBean(var user: String = "",
|
||||
var pass: String = "",
|
||||
var level: Int = DEFAULT_LEVEL)
|
||||
}
|
||||
|
||||
data class Response(var type: String)
|
||||
}
|
||||
|
||||
data class StreamSettingsBean(var network: String,
|
||||
var security: String,
|
||||
var tcpSettings: TcpSettingsBean?,
|
||||
var kcpSettings: KcpSettingsBean?,
|
||||
var wsSettings: WsSettingsBean?,
|
||||
var httpSettings: HttpSettingsBean?,
|
||||
var tlsSettings: TlsSettingsBean?,
|
||||
var quicSettings: QuicSettingBean?
|
||||
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
|
||||
var security: String = "",
|
||||
var tcpSettings: TcpSettingsBean? = null,
|
||||
var kcpSettings: KcpSettingsBean? = null,
|
||||
var wsSettings: WsSettingsBean? = null,
|
||||
var httpSettings: HttpSettingsBean? = null,
|
||||
var tlsSettings: TlsSettingsBean? = null,
|
||||
var quicSettings: QuicSettingBean? = null,
|
||||
var xtlsSettings: TlsSettingsBean? = null,
|
||||
var grpcSettings: GrpcSettingsBean? = null,
|
||||
val dsSettings: Any? = null,
|
||||
val sockopt: Any? = null
|
||||
) {
|
||||
|
||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) {
|
||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
|
||||
val acceptProxyProtocol: Boolean? = null) {
|
||||
data class HeaderBean(var type: String = "none",
|
||||
var request: Any? = null,
|
||||
var response: Any? = null)
|
||||
var request: RequestBean? = null,
|
||||
var response: Any? = null) {
|
||||
data class RequestBean(var path: List<String> = ArrayList(),
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
val version: String? = null,
|
||||
val method: String? = null) {
|
||||
data class HeadersBean(var Host: List<String> = ArrayList(),
|
||||
@SerializedName("User-Agent")
|
||||
val userAgent: List<String>? = null,
|
||||
@SerializedName("Accept-Encoding")
|
||||
val acceptEncoding: List<String>? = null,
|
||||
val Connection: List<String>? = null,
|
||||
val Pragma: String? = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class KcpSettingsBean(var mtu: Int = 1350,
|
||||
var tti: Int = 20,
|
||||
var tti: Int = 50,
|
||||
var uplinkCapacity: Int = 12,
|
||||
var downlinkCapacity: Int = 100,
|
||||
var congestion: Boolean = false,
|
||||
var readBufferSize: Int = 1,
|
||||
var writeBufferSize: Int = 1,
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
var header: HeaderBean = HeaderBean(),
|
||||
var seed: String? = null) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class WsSettingsBean(var path: String = "",
|
||||
var headers: HeadersBean = HeadersBean()) {
|
||||
var headers: HeadersBean = HeadersBean(),
|
||||
val maxEarlyData: Int? = null,
|
||||
val useBrowserForwarding: Boolean? = null,
|
||||
val acceptProxyProtocol: Boolean? = null) {
|
||||
data class HeadersBean(var Host: String = "")
|
||||
}
|
||||
|
||||
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
||||
var path: String = "")
|
||||
|
||||
data class TlsSettingsBean(var allowInsecure: Boolean = true,
|
||||
var serverName: String = "")
|
||||
data class TlsSettingsBean(var allowInsecure: Boolean = false,
|
||||
var serverName: String = "",
|
||||
val alpn: List<String>? = null,
|
||||
val minVersion: String? = null,
|
||||
val maxVersion: String? = null,
|
||||
val preferServerCipherSuites: Boolean? = null,
|
||||
val cipherSuites: String? = null,
|
||||
val fingerprint: String? = null,
|
||||
val certificates: List<Any>? = null,
|
||||
val disableSystemRoot: Boolean? = null,
|
||||
val enableSessionResumption: Boolean? = null)
|
||||
|
||||
data class QuicSettingBean(var security: String = "none",
|
||||
var key: String = "",
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class GrpcSettingsBean(var serviceName: String = "",
|
||||
var multiMode: Boolean? = null)
|
||||
|
||||
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
|
||||
quicSecurity: String?, key: String?, mode: String?, serviceName: String?): String {
|
||||
var sni = ""
|
||||
network = transport
|
||||
when (network) {
|
||||
"tcp" -> {
|
||||
val tcpSetting = TcpSettingsBean()
|
||||
if (headerType == HTTP) {
|
||||
tcpSetting.header.type = HTTP
|
||||
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
|
||||
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
|
||||
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
|
||||
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() }.filter { it.isNotEmpty() }
|
||||
sni = h2Setting.host.getOrNull(0) ?: sni
|
||||
h2Setting.path = path ?: "/"
|
||||
httpSettings = h2Setting
|
||||
}
|
||||
"quic" -> {
|
||||
val quicsetting = QuicSettingBean()
|
||||
quicsetting.security = quicSecurity ?: "none"
|
||||
quicsetting.key = key ?: ""
|
||||
quicsetting.header.type = headerType ?: "none"
|
||||
quicSettings = quicsetting
|
||||
}
|
||||
"grpc" -> {
|
||||
val grpcSetting = GrpcSettingsBean()
|
||||
grpcSetting.multiMode = mode == "multi"
|
||||
grpcSetting.serviceName = serviceName ?: ""
|
||||
sni = host ?: ""
|
||||
grpcSettings = grpcSetting
|
||||
}
|
||||
}
|
||||
return sni
|
||||
}
|
||||
|
||||
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String) {
|
||||
security = streamSecurity
|
||||
val tlsSetting = TlsSettingsBean(
|
||||
allowInsecure = allowInsecure,
|
||||
serverName = sni
|
||||
)
|
||||
if (security == TLS) {
|
||||
tlsSettings = tlsSetting
|
||||
} else if (security == XTLS) {
|
||||
xtlsSettings = tlsSetting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class MuxBean(var enabled: Boolean)
|
||||
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
|
||||
|
||||
fun getServerAddress(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
return settings?.vnext?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
return settings?.servers?.get(0)?.address
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getServerPort(): Int? {
|
||||
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
|
||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
||||
return settings?.vnext?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.SOCKS.name, true)
|
||||
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||
return settings?.servers?.get(0)?.port
|
||||
}
|
||||
return 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,
|
||||
var hosts: Map<String, String>?=null
|
||||
data class DnsBean(var servers: ArrayList<Any>? = null,
|
||||
var hosts: Map<String, Any>? = null,
|
||||
val clientIp: String? = null,
|
||||
val disableCache: Boolean? = null,
|
||||
val queryStrategy: String? = null,
|
||||
val tag: String? = null
|
||||
) {
|
||||
data class ServersBean(var address: String = "",
|
||||
var port: Int = 0,
|
||||
var domains: List<String>?)
|
||||
var port: Int? = null,
|
||||
var domains: List<String>? = null,
|
||||
var expectIPs: List<String>? = null,
|
||||
val clientIp: String? = null)
|
||||
}
|
||||
|
||||
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 = "",
|
||||
var ip: ArrayList<String>? = null,
|
||||
var domain: ArrayList<String>? = null,
|
||||
var outboundTag: String = "",
|
||||
var balancerTag: 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>,
|
||||
var system: Any?=null) {
|
||||
var system: Any? = null) {
|
||||
data class LevelBean(
|
||||
var handshake: Int? = null,
|
||||
var connIdle: Int? = null,
|
||||
var uplinkOnly: Int? = null,
|
||||
var downlinkOnly: Int? = null)
|
||||
var handshake: Int? = null,
|
||||
var connIdle: Int? = null,
|
||||
var uplinkOnly: Int? = null,
|
||||
var downlinkOnly: Int? = null,
|
||||
val statsUserUplink: Boolean? = null,
|
||||
val statsUserDownlink: Boolean? = null,
|
||||
var bufferSize: Int? = null)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ data class VmessQRCode(var v: String = "",
|
||||
var add: String = "",
|
||||
var port: String = "",
|
||||
var id: String = "",
|
||||
var aid: String = "",
|
||||
var aid: String = "0",
|
||||
var net: String = "",
|
||||
var type: String = "",
|
||||
var host: String = "",
|
||||
var path: String = "",
|
||||
var tls: String = "")
|
||||
var tls: String = "",
|
||||
var sni: String = "")
|
||||
|
||||
@@ -1,244 +0,0 @@
|
||||
package com.v2ray.ang.extension
|
||||
|
||||
import android.app.Fragment
|
||||
import android.app.ProgressDialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.database.Cursor
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.widget.ListAdapter
|
||||
|
||||
|
||||
fun Context.alertView(
|
||||
title: String? = null,
|
||||
view: View,
|
||||
init: (KAlertDialogBuilder.() -> Unit)? = null
|
||||
) = KAlertDialogBuilder(this).apply {
|
||||
if (title != null) title(title)
|
||||
if (title != null) customView(view)
|
||||
if (init != null) init()
|
||||
}
|
||||
|
||||
fun Fragment.alert(
|
||||
message: String,
|
||||
title: String? = null,
|
||||
init: (KAlertDialogBuilder.() -> Unit)? = null
|
||||
) = activity.alert(message, title, init)
|
||||
|
||||
fun Context.alert(
|
||||
message: String,
|
||||
title: String? = null,
|
||||
init: (KAlertDialogBuilder.() -> Unit)? = null
|
||||
) = KAlertDialogBuilder(this).apply {
|
||||
if (title != null) title(title)
|
||||
message(message)
|
||||
if (init != null) init()
|
||||
}
|
||||
|
||||
fun Fragment.alert(
|
||||
message: Int,
|
||||
title: Int? = null,
|
||||
init: (KAlertDialogBuilder.() -> Unit)? = null
|
||||
) = activity.alert(message, title, init)
|
||||
|
||||
fun Context.alert(
|
||||
message: Int,
|
||||
title: Int? = null,
|
||||
init: (KAlertDialogBuilder.() -> Unit)? = null
|
||||
) = KAlertDialogBuilder(this).apply {
|
||||
if (title != null) title(title)
|
||||
message(message)
|
||||
if (init != null) init()
|
||||
}
|
||||
|
||||
|
||||
fun Fragment.alert(init: KAlertDialogBuilder.() -> Unit): KAlertDialogBuilder = activity.alert(init)
|
||||
|
||||
fun Context.alert(init: KAlertDialogBuilder.() -> Unit) = KAlertDialogBuilder(this).apply { init() }
|
||||
|
||||
fun Fragment.progressDialog(
|
||||
message: Int? = null,
|
||||
title: Int? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = activity.progressDialog(message, title, init)
|
||||
|
||||
fun Context.progressDialog(
|
||||
message: Int? = null,
|
||||
title: Int? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = progressDialog(false, message?.let { getString(it) }, title?.let { getString(it) }, init)
|
||||
|
||||
fun Fragment.indeterminateProgressDialog(
|
||||
message: Int? = null,
|
||||
title: Int? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = activity.progressDialog(message, title, init)
|
||||
|
||||
fun Context.indeterminateProgressDialog(
|
||||
message: Int? = null,
|
||||
title: Int? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = progressDialog(true, message?.let { getString(it) }, title?.let { getString(it) }, init)
|
||||
|
||||
fun Fragment.progressDialog(
|
||||
message: String? = null,
|
||||
title: String? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = activity.progressDialog(message, title, init)
|
||||
|
||||
fun Context.progressDialog(
|
||||
message: String? = null,
|
||||
title: String? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = progressDialog(false, message, title, init)
|
||||
|
||||
fun Fragment.indeterminateProgressDialog(
|
||||
message: String? = null,
|
||||
title: String? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = activity.indeterminateProgressDialog(message, title, init)
|
||||
|
||||
fun Context.indeterminateProgressDialog(
|
||||
message: String? = null,
|
||||
title: String? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = progressDialog(true, message, title, init)
|
||||
|
||||
private fun Context.progressDialog(
|
||||
indeterminate: Boolean,
|
||||
message: String? = null,
|
||||
title: String? = null,
|
||||
init: (ProgressDialog.() -> Unit)? = null
|
||||
) = ProgressDialog(this).apply {
|
||||
isIndeterminate = indeterminate
|
||||
if (!indeterminate) setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
|
||||
if (message != null) setMessage(message)
|
||||
if (title != null) setTitle(title)
|
||||
if (init != null) init()
|
||||
show()
|
||||
}
|
||||
|
||||
fun Fragment.selector(
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
onClick: (Int) -> Unit
|
||||
): Unit = activity.selector(title, items, onClick)
|
||||
|
||||
fun Context.selector(
|
||||
title: CharSequence? = null,
|
||||
items: List<CharSequence>,
|
||||
onClick: (Int) -> Unit
|
||||
) {
|
||||
with(KAlertDialogBuilder(this)) {
|
||||
if (title != null) title(title)
|
||||
items(items, onClick)
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
class KAlertDialogBuilder(val ctx: Context) {
|
||||
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
|
||||
protected var dialog: AlertDialog? = null
|
||||
|
||||
fun dismiss() {
|
||||
dialog?.dismiss()
|
||||
}
|
||||
|
||||
fun show(): KAlertDialogBuilder {
|
||||
dialog = builder.create()
|
||||
dialog!!.show()
|
||||
return this
|
||||
}
|
||||
|
||||
fun title(title: CharSequence) {
|
||||
builder.setTitle(title)
|
||||
}
|
||||
|
||||
fun title(resource: Int) {
|
||||
builder.setTitle(resource)
|
||||
}
|
||||
|
||||
fun message(title: CharSequence) {
|
||||
builder.setMessage(title)
|
||||
}
|
||||
|
||||
fun message(resource: Int) {
|
||||
builder.setMessage(resource)
|
||||
}
|
||||
|
||||
fun icon(icon: Int) {
|
||||
builder.setIcon(icon)
|
||||
}
|
||||
|
||||
fun icon(icon: Drawable) {
|
||||
builder.setIcon(icon)
|
||||
}
|
||||
|
||||
fun customTitle(title: View) {
|
||||
builder.setCustomTitle(title)
|
||||
}
|
||||
|
||||
fun customView(view: View) {
|
||||
builder.setView(view)
|
||||
}
|
||||
|
||||
fun cancellable(value: Boolean = true) {
|
||||
builder.setCancelable(value)
|
||||
}
|
||||
|
||||
fun onCancel(f: () -> Unit) {
|
||||
builder.setOnCancelListener { f() }
|
||||
}
|
||||
|
||||
fun onKey(f: (keyCode: Int, e: KeyEvent) -> Boolean) {
|
||||
builder.setOnKeyListener({ dialog, keyCode, event -> f(keyCode, event) })
|
||||
}
|
||||
|
||||
fun neutralButton(textResource: Int = android.R.string.ok, f: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
neutralButton(ctx.getString(textResource), f)
|
||||
}
|
||||
|
||||
fun neutralButton(title: String, f: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
builder.setNeutralButton(title, { dialog, which -> dialog.f() })
|
||||
}
|
||||
|
||||
fun positiveButton(textResource: Int = android.R.string.ok, f: DialogInterface.() -> Unit) {
|
||||
positiveButton(ctx.getString(textResource), f)
|
||||
}
|
||||
|
||||
fun positiveButton(title: String, f: DialogInterface.() -> Unit) {
|
||||
builder.setPositiveButton(title, { dialog, which -> dialog.f() })
|
||||
}
|
||||
|
||||
fun negativeButton(textResource: Int = android.R.string.cancel, f: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
negativeButton(ctx.getString(textResource), f)
|
||||
}
|
||||
|
||||
fun negativeButton(title: String, f: DialogInterface.() -> Unit = { dismiss() }) {
|
||||
builder.setNegativeButton(title, { dialog, which -> dialog.f() })
|
||||
}
|
||||
|
||||
fun items(itemsId: Int, f: (which: Int) -> Unit) {
|
||||
items(ctx.resources!!.getTextArray(itemsId), f)
|
||||
}
|
||||
|
||||
fun items(items: List<CharSequence>, f: (which: Int) -> Unit) {
|
||||
items(items.toTypedArray(), f)
|
||||
}
|
||||
|
||||
fun items(items: Array<CharSequence>, f: (which: Int) -> Unit) {
|
||||
builder.setItems(items, { dialog, which -> f(which) })
|
||||
}
|
||||
|
||||
fun adapter(adapter: ListAdapter, f: (which: Int) -> Unit) {
|
||||
builder.setAdapter(adapter, { dialog, which -> f(which) })
|
||||
}
|
||||
|
||||
fun adapter(cursor: Cursor, labelColumn: String, f: (which: Int) -> Unit) {
|
||||
builder.setCursor(cursor, { dialog, which -> f(which) }, labelColumn)
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,11 @@ package com.v2ray.ang.extension
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import com.v2ray.ang.AngApplication
|
||||
import me.dozen.dpreference.DPreference
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import org.json.JSONObject
|
||||
import java.net.URI
|
||||
import java.net.URLConnection
|
||||
|
||||
/**
|
||||
@@ -14,11 +16,19 @@ import java.net.URLConnection
|
||||
val Context.v2RayApplication: AngApplication
|
||||
get() = applicationContext as AngApplication
|
||||
|
||||
val Context.defaultDPreference: DPreference
|
||||
get() = v2RayApplication.defaultDPreference
|
||||
fun Context.toast(message: Int): Toast = ToastCompat
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
fun Context.toast(message: CharSequence): Toast = ToastCompat
|
||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
.apply {
|
||||
show()
|
||||
}
|
||||
|
||||
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)!!
|
||||
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
|
||||
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
|
||||
|
||||
const val threshold = 1000
|
||||
@@ -65,3 +75,6 @@ private fun Float.toShortString(): String {
|
||||
|
||||
val URLConnection.responseLength: Long
|
||||
get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong()
|
||||
|
||||
val URI.idnHost: String
|
||||
get() = (host!!).replace("[", "").replace("]", "")
|
||||
@@ -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.text.TextUtils
|
||||
import com.google.zxing.WriterException
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class TaskerReceiver : BroadcastReceiver() {
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
|
||||
try {
|
||||
@@ -21,9 +26,10 @@ class TaskerReceiver : BroadcastReceiver() {
|
||||
return
|
||||
} else if (switch) {
|
||||
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
|
||||
Utils.startVService(context)
|
||||
Utils.startVServiceFromToggle(context)
|
||||
} else {
|
||||
Utils.startVService(context, guid)
|
||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
V2RayServiceManager.startV2Ray(context)
|
||||
}
|
||||
} else {
|
||||
Utils.stopVService(context)
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.widget.RemoteViews
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.AppConfig
|
||||
@@ -21,16 +22,33 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
|
||||
}
|
||||
|
||||
|
||||
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
|
||||
val intent = Intent(context, WidgetProvider::class.java)
|
||||
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val pendingIntent = PendingIntent.getBroadcast(
|
||||
context,
|
||||
R.id.layout_switch,
|
||||
intent,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
})
|
||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||
if (isRunning) {
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme)
|
||||
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)
|
||||
remoteViews.setInt(
|
||||
R.id.layout_switch,
|
||||
"setBackgroundResource",
|
||||
R.drawable.ic_rounded_corner_grey
|
||||
)
|
||||
}
|
||||
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
@@ -50,15 +68,16 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
Utils.startVServiceFromToggle(context)
|
||||
}
|
||||
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,10 @@ import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.N)
|
||||
class QSTileService : TileService() {
|
||||
|
||||
@@ -27,7 +25,7 @@ class QSTileService : TileService() {
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
|
||||
} else if (state == Tile.STATE_ACTIVE) {
|
||||
qsTile?.state = Tile.STATE_ACTIVE
|
||||
qsTile?.label = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
|
||||
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,5 +11,4 @@ interface ServiceControl {
|
||||
|
||||
fun vpnProtect(socket: Int): Boolean
|
||||
|
||||
fun vpnSendFd()
|
||||
}
|
||||
|
||||
@@ -37,10 +37,6 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun vpnSendFd() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -10,19 +10,26 @@ 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 androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import android.util.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.extension.toSpeedString
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.ui.MainActivity
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import libv2ray.Libv2ray
|
||||
import libv2ray.V2RayPoint
|
||||
import libv2ray.V2RayVPNServiceSupportsSet
|
||||
@@ -39,6 +46,8 @@ object V2RayServiceManager {
|
||||
|
||||
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) {
|
||||
@@ -50,14 +59,20 @@ object V2RayServiceManager {
|
||||
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, mode: String) {
|
||||
val intent = if (mode == "VPN") {
|
||||
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)
|
||||
@@ -78,7 +93,7 @@ object V2RayServiceManager {
|
||||
serviceControl.stopService()
|
||||
0
|
||||
} catch (e: Exception) {
|
||||
Log.d(serviceControl.getService().packageName, e.toString())
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
-1
|
||||
}
|
||||
}
|
||||
@@ -106,26 +121,21 @@ object V2RayServiceManager {
|
||||
startSpeedNotification()
|
||||
0
|
||||
} catch (e: Exception) {
|
||||
Log.d(serviceControl.getService().packageName, e.toString())
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendFd(): Long {
|
||||
val serviceControl = serviceControl?.get() ?: return -1
|
||||
try {
|
||||
serviceControl.vpnSendFd()
|
||||
} catch (e: Exception) {
|
||||
Log.d(serviceControl.getService().packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -134,18 +144,20 @@ object V2RayServiceManager {
|
||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
service.registerReceiver(mMsgReceive, mFilter)
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
}
|
||||
|
||||
v2rayPoint.configureFileContent = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
|
||||
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
|
||||
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
|
||||
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())
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
}
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
@@ -162,10 +174,12 @@ object V2RayServiceManager {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +189,7 @@ object V2RayServiceManager {
|
||||
try {
|
||||
service.unregisterReceiver(mMsgReceive)
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
Log.d(ANG_PACKAGE, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,11 +221,11 @@ object V2RayServiceManager {
|
||||
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
stopSpeedNotification()
|
||||
}
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
startSpeedNotification()
|
||||
}
|
||||
}
|
||||
@@ -223,15 +237,23 @@ object V2RayServiceManager {
|
||||
val startMainIntent = Intent(service, MainActivity::class.java)
|
||||
val contentPendingIntent = PendingIntent.getActivity(service,
|
||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
})
|
||||
|
||||
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
|
||||
stopV2RayIntent.`package` = ANG_PACKAGE
|
||||
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
||||
|
||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
|
||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
})
|
||||
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
@@ -244,7 +266,7 @@ object V2RayServiceManager {
|
||||
|
||||
mBuilder = NotificationCompat.Builder(service, channelId)
|
||||
.setSmallIcon(R.drawable.ic_v)
|
||||
.setContentTitle(service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
|
||||
.setContentTitle(currentConfig?.remarks)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setOngoing(true)
|
||||
.setShowWhen(false)
|
||||
@@ -281,7 +303,7 @@ object V2RayServiceManager {
|
||||
mSubscription = null
|
||||
}
|
||||
|
||||
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
|
||||
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)
|
||||
@@ -305,13 +327,12 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
fun startSpeedNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
if (mSubscription == null &&
|
||||
v2rayPoint.isRunning &&
|
||||
service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
|
||||
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
|
||||
var lastZeroSpeed = false
|
||||
val outboundTags = service.defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
|
||||
outboundTags.remove(TAG_DIRECT)
|
||||
val outboundTags = currentConfig?.getAllOutboundTags()
|
||||
outboundTags?.remove(TAG_DIRECT)
|
||||
|
||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.subscribe {
|
||||
@@ -319,7 +340,7 @@ object V2RayServiceManager {
|
||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||
var proxyTotal = 0L
|
||||
val text = StringBuilder()
|
||||
outboundTags.forEach {
|
||||
outboundTags?.forEach {
|
||||
val up = v2rayPoint.queryStats(it, "uplink")
|
||||
val down = v2rayPoint.queryStats(it, "downlink")
|
||||
if (up + down > 0) {
|
||||
@@ -332,7 +353,7 @@ object V2RayServiceManager {
|
||||
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
|
||||
if (!zeroSpeed || !lastZeroSpeed) {
|
||||
if (proxyTotal == 0L) {
|
||||
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
|
||||
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
|
||||
}
|
||||
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds)
|
||||
@@ -355,13 +376,10 @@ object V2RayServiceManager {
|
||||
}
|
||||
|
||||
fun stopSpeedNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
if (mSubscription != null) {
|
||||
mSubscription?.unsubscribe() //stop queryStats
|
||||
mSubscription = null
|
||||
|
||||
val cfName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
|
||||
updateNotification(cfName, 0, 0)
|
||||
updateNotification(currentConfig?.remarks, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,22 @@ import android.net.*
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.StrictMode
|
||||
import android.support.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresApi
|
||||
import android.util.Log
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.ui.PerAppProxyActivity
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.jetbrains.anko.doAsync
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
class V2RayVpnService : VpnService(), ServiceControl {
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
private lateinit var mInterface: ParcelFileDescriptor
|
||||
|
||||
/**
|
||||
@@ -48,7 +51,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
override fun onAvailable(network: Network) {
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
// it's a good idea to refresh capabilities
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
@@ -58,8 +61,6 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
}
|
||||
|
||||
private var listeningForDefaultNetwork = false
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
@@ -79,7 +80,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
V2RayServiceManager.cancelNotification()
|
||||
stopV2Ray()
|
||||
}
|
||||
|
||||
private fun setup(parameters: String) {
|
||||
@@ -92,8 +93,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
// If the old interface has exactly the same parameters, use it!
|
||||
// Configure a builder while parsing the parameters.
|
||||
val builder = Builder()
|
||||
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
val routingMode = defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||
|
||||
parameters.split(" ")
|
||||
.map { it.split(",") }
|
||||
@@ -121,18 +122,19 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
if(!enableLocalDns) {
|
||||
Utils.getRemoteDnsServers(defaultDPreference)
|
||||
.forEach {
|
||||
builder.addDnsServer(it)
|
||||
}
|
||||
Utils.getVpnDnsServers()
|
||||
.forEach {
|
||||
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 &&
|
||||
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
|
||||
val apps = defaultDPreference.getPrefStringSet(PerAppProxyActivity.PREF_PER_APP_PROXY_SET, null)
|
||||
val bypassApps = defaultDPreference.getPrefBoolean(PerAppProxyActivity.PREF_BYPASS_APPS, false)
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
|
||||
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
|
||||
apps?.forEach {
|
||||
try {
|
||||
if (bypassApps)
|
||||
@@ -153,8 +155,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = true
|
||||
try {
|
||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
@@ -162,7 +167,14 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@@ -170,10 +182,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
val fd = mInterface.fileDescriptor
|
||||
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
|
||||
|
||||
doAsync {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
var tries = 0
|
||||
while (true) try {
|
||||
Thread.sleep(50L shl tries)
|
||||
Thread.sleep(1000L shl tries)
|
||||
Log.d(packageName, "sendFd tries: $tries")
|
||||
LocalSocket().use { localSocket ->
|
||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||
@@ -200,9 +212,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
// val emptyInfo = VpnNetworkInfo()
|
||||
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
||||
// saveVpnNetworkInfo(configName, info)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && listeningForDefaultNetwork) {
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
try {
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
} catch (ignored: Exception) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
V2RayServiceManager.stopV2rayPoint()
|
||||
@@ -240,7 +255,4 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
||||
return protect(socket)
|
||||
}
|
||||
|
||||
override fun vpnSendFd() {
|
||||
sendFd()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.support.v7.app.AppCompatActivity
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.view.MenuItem
|
||||
|
||||
abstract class BaseActivity : AppCompatActivity() {
|
||||
@@ -11,4 +11,4 @@ abstract class BaseActivity : AppCompatActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,203 +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.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 (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.sub_setting)
|
||||
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
|
||||
import android.support.v4.app.Fragment
|
||||
import android.support.v4.app.FragmentManager
|
||||
import android.support.v4.app.FragmentStatePagerAdapter
|
||||
class FragmentAdapter(fragmentActivity: FragmentActivity, private val mFragments: List<Fragment>) :
|
||||
FragmentStateAdapter(fragmentActivity) {
|
||||
|
||||
class FragmentAdapter(fm: FragmentManager, private val mFragments: List<Fragment>, private val mTitles: List<String>) : FragmentStatePagerAdapter(fm) {
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return mFragments[position]
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
override fun getItemCount(): Int {
|
||||
return mFragments.size
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence? {
|
||||
return mTitles[position]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,21 +7,25 @@ import android.text.method.ScrollingMovementMethod
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_logcat.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
class LogcatActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityLogcatBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_logcat)
|
||||
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
title = getString(R.string.title_logcat)
|
||||
|
||||
@@ -32,9 +36,9 @@ class LogcatActivity : BaseActivity() {
|
||||
private fun logcat(shouldFlushLog: Boolean) {
|
||||
|
||||
try {
|
||||
pb_waiting.visibility = View.VISIBLE
|
||||
binding.pbWaiting.visibility = View.VISIBLE
|
||||
|
||||
doAsync {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
if (shouldFlushLog) {
|
||||
val lst = LinkedHashSet<String>()
|
||||
lst.add("logcat")
|
||||
@@ -48,17 +52,17 @@ class LogcatActivity : BaseActivity() {
|
||||
lst.add("-v")
|
||||
lst.add("time")
|
||||
lst.add("-s")
|
||||
lst.add("GoLog,tun2socks,com.v2ray.ang")
|
||||
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
|
||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
||||
// val bufferedReader = BufferedReader(
|
||||
// InputStreamReader(process.inputStream))
|
||||
// val allText = bufferedReader.use(BufferedReader::readText)
|
||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||
uiThread {
|
||||
tv_logcat.text = allText
|
||||
tv_logcat.movementMethod = ScrollingMovementMethod()
|
||||
pb_waiting.visibility = View.GONE
|
||||
Handler(Looper.getMainLooper()).post { sv_logcat.fullScroll(View.FOCUS_DOWN) }
|
||||
launch(Dispatchers.Main) {
|
||||
binding.tvLogcat.text = allText
|
||||
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
|
||||
binding.pbWaiting.visibility = View.GONE
|
||||
Handler(Looper.getMainLooper()).post { binding.svLogcat.fullScroll(View.FOCUS_DOWN) }
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
@@ -66,14 +70,14 @@ class LogcatActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_logcat, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.copy_all -> {
|
||||
Utils.setClipboard(this, tv_logcat.text.toString())
|
||||
Utils.setClipboard(this, binding.tvLogcat.text.toString())
|
||||
toast(R.string.toast_success)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -1,196 +1,182 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.*
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.os.Bundle
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.KeyEvent
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import org.jetbrains.anko.*
|
||||
import java.lang.ref.SoftReference
|
||||
import java.net.URL
|
||||
import android.content.IntentFilter
|
||||
import android.support.design.widget.NavigationView
|
||||
import android.support.v4.view.GravityCompat
|
||||
import android.support.v7.app.ActionBarDrawerToggle
|
||||
import android.support.v7.widget.helper.ItemTouchHelper
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
//import com.v2ray.ang.InappBuyActivity
|
||||
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.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import libv2ray.Libv2ray
|
||||
import me.drakeet.support.toast.ToastCompat
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.util.AngConfigManager.configs
|
||||
import kotlinx.coroutines.*
|
||||
import libv2ray.Libv2ray
|
||||
|
||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||
companion object {
|
||||
private const val REQUEST_CODE_VPN_PREPARE = 0
|
||||
private const val REQUEST_SCAN = 1
|
||||
private const val REQUEST_FILE_CHOOSER = 2
|
||||
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 lateinit var binding: ActivityMainBinding
|
||||
|
||||
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 val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
startV2Ray()
|
||||
}
|
||||
}
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
private val testingJobs = ArrayList<Job>()
|
||||
val mainViewModel: MainViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
title = getString(R.string.title_server)
|
||||
setSupportActionBar(toolbar)
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
fab.setOnClickListener {
|
||||
if (isRunning) {
|
||||
binding.fab.setOnClickListener {
|
||||
if (mainViewModel.isRunning.value == true) {
|
||||
Utils.stopVService(this)
|
||||
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
|
||||
} else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null) {
|
||||
startV2Ray()
|
||||
} else {
|
||||
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
|
||||
requestVpnPermission.launch(intent)
|
||||
}
|
||||
} else {
|
||||
startV2Ray()
|
||||
}
|
||||
}
|
||||
layout_test.setOnClickListener {
|
||||
if (isRunning) {
|
||||
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
||||
|
||||
tv_test_state.text = getString(R.string.connection_test_testing)
|
||||
doAsync {
|
||||
val result = Utils.testConnection(this@MainActivity, socksPort)
|
||||
uiThread {
|
||||
tv_test_state.text = Utils.getEditable(result)
|
||||
}
|
||||
}
|
||||
binding.layoutTest.setOnClickListener {
|
||||
if (mainViewModel.isRunning.value == true) {
|
||||
binding.tvTestState.text = getString(R.string.connection_test_testing)
|
||||
mainViewModel.testCurrentServerRealPing()
|
||||
} else {
|
||||
// tv_test_state.text = getString(R.string.connection_test_fail)
|
||||
}
|
||||
}
|
||||
|
||||
recycler_view.setHasFixedSize(true)
|
||||
recycler_view.layoutManager = LinearLayoutManager(this)
|
||||
recycler_view.adapter = adapter
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
val callback = SimpleItemTouchHelperCallback(adapter)
|
||||
mItemTouchHelper = ItemTouchHelper(callback)
|
||||
mItemTouchHelper?.attachToRecyclerView(recycler_view)
|
||||
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
|
||||
|
||||
|
||||
val toggle = ActionBarDrawerToggle(
|
||||
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
|
||||
drawer_layout.addDrawerListener(toggle)
|
||||
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
|
||||
binding.drawerLayout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
nav_view.setNavigationItemSelectedListener(this)
|
||||
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
|
||||
binding.navView.setNavigationItemSelectedListener(this)
|
||||
binding.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) { binding.tvTestState.text = it }
|
||||
mainViewModel.isRunning.observe(this) {
|
||||
val isRunning = it ?: return@observe
|
||||
adapter.isRunning = isRunning
|
||||
if (isRunning) {
|
||||
binding.fab.setImageResource(R.drawable.ic_v)
|
||||
binding.tvTestState.text = getString(R.string.connection_connected)
|
||||
binding.layoutTest.isFocusable = true
|
||||
} else {
|
||||
binding.fab.setImageResource(R.drawable.ic_v_idle)
|
||||
binding.tvTestState.text = getString(R.string.connection_not_connected)
|
||||
binding.layoutTest.isFocusable = false
|
||||
}
|
||||
hideCircle()
|
||||
}
|
||||
mainViewModel.startListenBroadcast()
|
||||
}
|
||||
|
||||
private fun migrateLegacy() {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
|
||||
if (result != null) {
|
||||
launch(Dispatchers.Main) {
|
||||
if (result) {
|
||||
toast(getString(R.string.migration_success))
|
||||
mainViewModel.reloadServerList()
|
||||
} else {
|
||||
toast(getString(R.string.migration_fail))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startV2Ray() {
|
||||
if (AngConfigManager.configs.index < 0) {
|
||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
showCircle()
|
||||
// toast(R.string.toast_services_start)
|
||||
if (!Utils.startVService(this)) {
|
||||
hideCircle()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
isRunning = false
|
||||
|
||||
// val intent = Intent(this.applicationContext, V2RayVpnService::class.java)
|
||||
// intent.`package` = AppConfig.ANG_PACKAGE
|
||||
// bindService(intent, mConnection, BIND_AUTO_CREATE)
|
||||
|
||||
mMsgReceive = ReceiveMessageHandler(this@MainActivity)
|
||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
||||
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (mMsgReceive != null) {
|
||||
unregisterReceiver(mMsgReceive)
|
||||
mMsgReceive = null
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(this)
|
||||
hideCircle()
|
||||
}
|
||||
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
adapter.updateConfigList()
|
||||
mainViewModel.reloadServerList()
|
||||
}
|
||||
|
||||
public override fun onPause() {
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
REQUEST_CODE_VPN_PREPARE ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
startV2Ray()
|
||||
}
|
||||
REQUEST_SCAN ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
|
||||
}
|
||||
REQUEST_FILE_CHOOSER -> {
|
||||
val uri = data?.data
|
||||
if (resultCode == RESULT_OK && uri != null) {
|
||||
readContentFromUri(uri)
|
||||
}
|
||||
}
|
||||
REQUEST_SCAN_URL ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
importConfigCustomUrl(data?.getStringExtra("SCAN_RESULT"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_main, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.import_qrcode -> {
|
||||
importQRcode(REQUEST_SCAN)
|
||||
importQRcode(true)
|
||||
true
|
||||
}
|
||||
R.id.import_clipboard -> {
|
||||
@@ -198,18 +184,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
true
|
||||
}
|
||||
R.id.import_manually_vmess -> {
|
||||
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning)
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
|
||||
setClass(this, ServerActivity::class.java))
|
||||
true
|
||||
}
|
||||
R.id.import_manually_ss -> {
|
||||
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning)
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
|
||||
setClass(this, ServerActivity::class.java))
|
||||
true
|
||||
}
|
||||
R.id.import_manually_socks -> {
|
||||
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning)
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
|
||||
setClass(this, ServerActivity::class.java))
|
||||
true
|
||||
}
|
||||
R.id.import_config_custom_clipboard -> {
|
||||
@@ -225,7 +211,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
true
|
||||
}
|
||||
R.id.import_config_custom_url_scan -> {
|
||||
importQRcode(REQUEST_SCAN_URL)
|
||||
importQRcode(false)
|
||||
true
|
||||
}
|
||||
|
||||
@@ -240,8 +226,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
R.id.export_all -> {
|
||||
if (AngConfigManager.shareAll2Clipboard() == 0) {
|
||||
//remove toast, otherwise it will block previous warning message
|
||||
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
@@ -249,35 +235,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
R.id.ping_all -> {
|
||||
testingJobs.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
testingJobs.clear()
|
||||
Utils.closeAllTcpSockets()
|
||||
for (k in 0 until configs.vmess.count()) {
|
||||
configs.vmess[k].testResult = ""
|
||||
adapter.updateConfigList()
|
||||
}
|
||||
for (k in 0 until configs.vmess.count()) {
|
||||
var serverAddress = configs.vmess[k].address
|
||||
var serverPort = configs.vmess[k].port
|
||||
if (configs.vmess[k].configType == EConfigType.CUSTOM.value) {
|
||||
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(applicationContext, configs.vmess[k].guid)
|
||||
?: continue
|
||||
serverAddress = serverOutbound.getServerAddress() ?: continue
|
||||
serverPort = serverOutbound.getServerPort() ?: continue
|
||||
}
|
||||
testingJobs.add(GlobalScope.launch(Dispatchers.IO) {
|
||||
configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing
|
||||
it.testResult = Utils.tcping(serverAddress, serverPort)
|
||||
val myJob = coroutineContext[Job]
|
||||
launch(Dispatchers.Main) {
|
||||
testingJobs.remove(myJob)
|
||||
adapter.updateSelectedItem(k)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
mainViewModel.testAllTcping()
|
||||
true
|
||||
}
|
||||
|
||||
@@ -296,7 +254,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
/**
|
||||
* import config from qrcode
|
||||
*/
|
||||
fun importQRcode(requestCode: Int): Boolean {
|
||||
fun importQRcode(forConfig: Boolean): Boolean {
|
||||
// try {
|
||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
||||
// .addCategory(Intent.CATEGORY_DEFAULT)
|
||||
@@ -306,7 +264,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
startActivityForResult<ScannerActivity>(requestCode)
|
||||
if (forConfig)
|
||||
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
|
||||
else
|
||||
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
|
||||
else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
@@ -314,6 +275,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
return true
|
||||
}
|
||||
|
||||
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
|
||||
}
|
||||
}
|
||||
|
||||
private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* import config from clipboard
|
||||
*/
|
||||
@@ -330,10 +303,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
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) {
|
||||
toast(R.string.toast_success)
|
||||
adapter.updateConfigList()
|
||||
mainViewModel.reloadServerList()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
@@ -392,9 +368,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
toast(R.string.toast_invalid_url)
|
||||
return false
|
||||
}
|
||||
doAsync {
|
||||
val configText = URL(url).readText()
|
||||
uiThread {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val configText = try {
|
||||
Utils.getUrlContentWithCustomUserAgent(url)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
importCustomizeConfig(configText)
|
||||
}
|
||||
}
|
||||
@@ -412,24 +393,30 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
: Boolean {
|
||||
try {
|
||||
toast(R.string.title_sub_update)
|
||||
val subItem = AngConfigManager.configs.subItem
|
||||
for (k in 0 until subItem.count()) {
|
||||
if (TextUtils.isEmpty(subItem[k].id)
|
||||
|| TextUtils.isEmpty(subItem[k].remarks)
|
||||
|| TextUtils.isEmpty(subItem[k].url)
|
||||
MmkvManager.decodeSubscriptions().forEach {
|
||||
if (TextUtils.isEmpty(it.first)
|
||||
|| TextUtils.isEmpty(it.second.remarks)
|
||||
|| TextUtils.isEmpty(it.second.url)
|
||||
) {
|
||||
continue
|
||||
return@forEach
|
||||
}
|
||||
val id = subItem[k].id
|
||||
val url = subItem[k].url
|
||||
val url = it.second.url
|
||||
if (!Utils.isValidUrl(url)) {
|
||||
continue
|
||||
return@forEach
|
||||
}
|
||||
Log.d("Main", url)
|
||||
doAsync {
|
||||
val configText = URL(url).readText()
|
||||
uiThread {
|
||||
importBatchConfig(Utils.decode(configText), id)
|
||||
Log.d(ANG_PACKAGE, url)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val configText = try {
|
||||
Utils.getUrlContentWithCustomUserAgent(url)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
launch(Dispatchers.Main) {
|
||||
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
importBatchConfig(Utils.decode(configText), it.first)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,14 +436,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
|
||||
try {
|
||||
startActivityForResult(
|
||||
Intent.createChooser(intent, getString(R.string.title_file_chooser)),
|
||||
REQUEST_FILE_CHOOSER)
|
||||
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
||||
} catch (ex: android.content.ActivityNotFoundException) {
|
||||
toast(R.string.toast_require_file_manager)
|
||||
}
|
||||
}
|
||||
|
||||
private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val uri = it.data?.data
|
||||
if (it.resultCode == RESULT_OK && uri != null) {
|
||||
readContentFromUri(uri)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read content from uri
|
||||
*/
|
||||
@@ -466,9 +458,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.subscribe {
|
||||
if (it) {
|
||||
try {
|
||||
contentResolver.openInputStream(uri).use {
|
||||
val configText = it?.bufferedReader()?.readText()
|
||||
importCustomizeConfig(configText)
|
||||
contentResolver.openInputStream(uri).use { input ->
|
||||
importCustomizeConfig(input?.bufferedReader()?.readText())
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -482,19 +473,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
* import customize config
|
||||
*/
|
||||
fun importCustomizeConfig(server: String?) {
|
||||
if (server == null) {
|
||||
return
|
||||
}
|
||||
if (!V2rayConfigUtil.isValidConfig(server)) {
|
||||
toast(R.string.toast_config_file_invalid)
|
||||
return
|
||||
}
|
||||
val resId = AngConfigManager.importCustomizeConfig(server)
|
||||
if (resId > 0) {
|
||||
toast(resId)
|
||||
} else {
|
||||
try {
|
||||
if (server == null || TextUtils.isEmpty(server)) {
|
||||
toast(R.string.toast_none_data)
|
||||
return
|
||||
}
|
||||
mainViewModel.appendCustomConfigServer(server)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,35 +497,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 {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
moveTaskToBack(false)
|
||||
@@ -545,7 +506,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
fun showCircle() {
|
||||
fabProgressCircle?.show()
|
||||
binding.fabProgressCircle.show()
|
||||
}
|
||||
|
||||
fun hideCircle() {
|
||||
@@ -553,8 +514,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
Observable.timer(300, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
if (fabProgressCircle.isShown) {
|
||||
fabProgressCircle.hide()
|
||||
if (binding.fabProgressCircle.isShown) {
|
||||
binding.fabProgressCircle.hide()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -562,8 +523,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
@@ -574,10 +535,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
when (item.itemId) {
|
||||
//R.id.server_profile -> activityClass = MainActivity::class.java
|
||||
R.id.sub_setting -> {
|
||||
startActivity<SubSettingActivity>()
|
||||
startActivity(Intent(this, SubSettingActivity::class.java))
|
||||
}
|
||||
R.id.settings -> {
|
||||
startActivity<SettingsActivity>("isRunning" to isRunning)
|
||||
startActivity(Intent(this, SettingsActivity::class.java)
|
||||
.putExtra("isRunning", mainViewModel.isRunning.value == true))
|
||||
}
|
||||
R.id.feedback -> {
|
||||
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
||||
@@ -589,10 +551,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
// startActivity<InappBuyActivity>()
|
||||
}
|
||||
R.id.logcat -> {
|
||||
startActivity<LogcatActivity>()
|
||||
startActivity(Intent(this, LogcatActivity::class.java))
|
||||
}
|
||||
}
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
binding.drawerLayout.closeDrawer(GravityCompat.START)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.text.TextUtils
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.databinding.ItemQrcodeBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
|
||||
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.helper.ItemTouchHelperAdapter
|
||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import kotlinx.android.synthetic.main.item_qrcode.view.*
|
||||
import kotlinx.android.synthetic.main.item_recycler_main.view.*
|
||||
import org.jetbrains.anko.*
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -30,150 +36,132 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
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 {
|
||||
mActivity.resources.getStringArray(R.array.share_method)
|
||||
}
|
||||
var isRunning = false
|
||||
|
||||
var changeable: Boolean = true
|
||||
set(value) {
|
||||
if (field == value)
|
||||
return
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
init {
|
||||
updateConfigList()
|
||||
}
|
||||
|
||||
override fun getItemCount() = configs.vmess.count() + 1
|
||||
override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val configType = EConfigType.fromInt(configs.vmess[position].configType)
|
||||
val remarks = configs.vmess[position].remarks
|
||||
val subid = configs.vmess[position].subid
|
||||
val address = configs.vmess[position].address
|
||||
val port = configs.vmess[position].port
|
||||
val test_result = configs.vmess[position].testResult
|
||||
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
||||
val config = mActivity.mainViewModel.serversCache.getOrElse(guid) { MmkvManager.decodeServerConfig(guid) } ?: return
|
||||
val outbound = config.getProxyOutbound()
|
||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||
|
||||
holder.name.text = remarks
|
||||
holder.radio.isChecked = (position == configs.index)
|
||||
holder.itemView.backgroundColor = Color.TRANSPARENT
|
||||
holder.test_result.text = test_result
|
||||
|
||||
if (TextUtils.isEmpty(subid)) {
|
||||
holder.subid.text = ""
|
||||
holder.itemMainBinding.tvName.text = config.remarks
|
||||
holder.itemMainBinding.btnRadio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
||||
if (aff?.testDelayMillis?:0L < 0L) {
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
|
||||
} else {
|
||||
holder.subid.text = "S"
|
||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||
}
|
||||
holder.itemMainBinding.tvSubscription.text = ""
|
||||
val json = subStorage?.decodeString(config.subscriptionId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
holder.itemMainBinding.tvSubscription.text = sub.remarks
|
||||
}
|
||||
|
||||
var shareOptions = share_method.asList()
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
holder.type.text = mActivity.getString(R.string.server_customize_config)
|
||||
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
|
||||
if (serverOutbound == null) {
|
||||
holder.statistics.text = ""
|
||||
} else {
|
||||
holder.statistics.text = "${serverOutbound.getServerAddress()} : ${serverOutbound.getServerPort()}"
|
||||
when (config.configType) {
|
||||
EConfigType.CUSTOM -> {
|
||||
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
|
||||
shareOptions = shareOptions.takeLast(1)
|
||||
}
|
||||
EConfigType.VLESS -> {
|
||||
holder.itemMainBinding.tvType.text = config.configType.name
|
||||
}
|
||||
else -> {
|
||||
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
||||
}
|
||||
shareOptions = shareOptions.takeLast(1)
|
||||
} else {
|
||||
holder.type.text = configType?.name?.toLowerCase()
|
||||
holder.statistics.text = "$address : $port"
|
||||
}
|
||||
holder.itemMainBinding.tvStatistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
|
||||
|
||||
holder.layout_share.setOnClickListener {
|
||||
mActivity.selector(null, shareOptions) { dialogInterface, i ->
|
||||
holder.itemMainBinding.layoutShare.setOnClickListener {
|
||||
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||
try {
|
||||
when (i) {
|
||||
0 -> {
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
shareFullContent(position)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
shareFullContent(guid)
|
||||
} else {
|
||||
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
|
||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
|
||||
|
||||
mActivity.alert {
|
||||
customView {
|
||||
linearLayout {
|
||||
addView(iv)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
|
||||
ivBinding.ivQcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
|
||||
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
if (AngConfigManager.share2Clipboard(position) == 0) {
|
||||
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
2 -> shareFullContent(position)
|
||||
2 -> shareFullContent(guid)
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
if (configType == EConfigType.VMESS) {
|
||||
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable)
|
||||
} else if (configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
|
||||
} else if (configType == EConfigType.SHADOWSOCKS) {
|
||||
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable)
|
||||
} else if (configType == EConfigType.SOCKS) {
|
||||
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
|
||||
}
|
||||
}
|
||||
holder.layout_remove.setOnClickListener {
|
||||
if (configs.index != position) {
|
||||
if (AngConfigManager.removeServer(position) == 0) {
|
||||
notifyItemRemoved(position)
|
||||
updateSelectedItem(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
holder.infoContainer.setOnClickListener {
|
||||
if (changeable) {
|
||||
AngConfigManager.setActiveServer(position)
|
||||
holder.itemMainBinding.layoutEdit.setOnClickListener {
|
||||
val intent = Intent().putExtra("guid", guid)
|
||||
.putExtra("isRunning", isRunning)
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
|
||||
} else {
|
||||
mActivity.showCircle()
|
||||
Utils.stopVService(mActivity)
|
||||
AngConfigManager.setActiveServer(position)
|
||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
mActivity.showCircle()
|
||||
if (!Utils.startVService(mActivity)) {
|
||||
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
|
||||
}
|
||||
}
|
||||
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
notifyItemRemoved(position)
|
||||
notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemMainBinding.infoContainer.setOnClickListener {
|
||||
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||
if (guid != selected) {
|
||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
|
||||
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
|
||||
if (isRunning) {
|
||||
mActivity.showCircle()
|
||||
Utils.stopVService(mActivity)
|
||||
Observable.timer(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
V2RayServiceManager.startV2Ray(mActivity)
|
||||
mActivity.hideCircle()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
if (holder is FooterViewHolder) {
|
||||
//if (activity?.defaultDPreference?.getPrefBoolean(AppConfig.PREF_INAPP_BUY_IS_PREMIUM, false)) {
|
||||
if (true) {
|
||||
holder.layout_edit.visibility = View.INVISIBLE
|
||||
holder.itemFooterBinding.layoutEdit.visibility = View.INVISIBLE
|
||||
} else {
|
||||
holder.layout_edit.setOnClickListener {
|
||||
Utils.openUri(mActivity, AppConfig.promotionUrl)
|
||||
holder.itemFooterBinding.layoutEdit.setOnClickListener {
|
||||
Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFullContent(position: Int) {
|
||||
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
|
||||
private fun shareFullContent(guid: String) {
|
||||
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
@@ -181,96 +169,63 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||
when (viewType) {
|
||||
return when (viewType) {
|
||||
VIEW_TYPE_ITEM ->
|
||||
return MainViewHolder(parent.context.layoutInflater
|
||||
.inflate(R.layout.item_recycler_main, parent, false))
|
||||
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
else ->
|
||||
return FooterViewHolder(parent.context.layoutInflater
|
||||
.inflate(R.layout.item_recycler_footer, parent, false))
|
||||
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), 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 {
|
||||
if (position == configs.vmess.count()) {
|
||||
return VIEW_TYPE_FOOTER
|
||||
return if (position == mActivity.mainViewModel.serverList.size) {
|
||||
VIEW_TYPE_FOOTER
|
||||
} else {
|
||||
return VIEW_TYPE_ITEM
|
||||
VIEW_TYPE_ITEM
|
||||
}
|
||||
}
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
|
||||
val subid = itemView.tv_subid
|
||||
val radio = itemView.btn_radio!!
|
||||
val name = itemView.tv_name!!
|
||||
val test_result = itemView.tv_test_result!!
|
||||
val type = itemView.tv_type!!
|
||||
val statistics = itemView.tv_statistics!!
|
||||
val infoContainer = itemView.info_container!!
|
||||
val layout_edit = itemView.layout_edit!!
|
||||
val layout_share = itemView.layout_share
|
||||
val layout_remove = itemView.layout_remove!!
|
||||
|
||||
override fun onItemSelected() {
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
fun onItemSelected() {
|
||||
itemView.setBackgroundColor(Color.LTGRAY)
|
||||
}
|
||||
|
||||
override fun onItemClear() {
|
||||
fun onItemClear() {
|
||||
itemView.setBackgroundColor(0)
|
||||
}
|
||||
}
|
||||
|
||||
class FooterViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
|
||||
val layout_edit = itemView.layout_edit!!
|
||||
class MainViewHolder(val itemMainBinding: ItemRecyclerMainBinding) :
|
||||
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
|
||||
|
||||
override fun onItemSelected() {
|
||||
itemView.setBackgroundColor(Color.LTGRAY)
|
||||
}
|
||||
|
||||
override fun onItemClear() {
|
||||
itemView.setBackgroundColor(0)
|
||||
}
|
||||
}
|
||||
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
|
||||
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder
|
||||
|
||||
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) {
|
||||
// positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeServer(position) == 0) {
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
mActivity.mainViewModel.removeServer(guid)
|
||||
notifyItemRemoved(position)
|
||||
// }
|
||||
// show()
|
||||
// }
|
||||
}
|
||||
updateSelectedItem(position)
|
||||
}
|
||||
|
||||
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
|
||||
AngConfigManager.swapServer(fromPosition, toPosition)
|
||||
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
|
||||
notifyItemMoved(fromPosition, toPosition)
|
||||
//notifyDataSetChanged()
|
||||
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
|
||||
// position is changed, since position is used by click callbacks, need to update range
|
||||
if (toPosition > fromPosition)
|
||||
notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
|
||||
else
|
||||
notifyItemRangeChanged(toPosition, fromPosition - toPosition + 1)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemMoveCompleted() {
|
||||
AngConfigManager.storeConfigFile()
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
@@ -12,11 +15,8 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import com.dinuscxj.itemdecoration.LinearDividerItemDecoration
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.AppManagerUtil
|
||||
import kotlinx.android.synthetic.main.activity_bypass_list.*
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import java.text.Collator
|
||||
@@ -24,34 +24,36 @@ import java.util.*
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.databinding.ActivityBypassListBinding
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
class PerAppProxyActivity : BaseActivity() {
|
||||
companion object {
|
||||
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||
}
|
||||
private lateinit var binding: ActivityBypassListBinding
|
||||
|
||||
private var adapter: PerAppProxyAdapter? = null
|
||||
private var appsAll: List<AppInfo>? = null
|
||||
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_bypass_list)
|
||||
binding = ActivityBypassListBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val dividerItemDecoration = LinearDividerItemDecoration(
|
||||
this, LinearDividerItemDecoration.LINEAR_DIVIDER_VERTICAL)
|
||||
recycler_view.addItemDecoration(dividerItemDecoration)
|
||||
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||
binding.recyclerView.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)
|
||||
.subscribeOn(Schedulers.io())
|
||||
@@ -64,8 +66,8 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
one.isSelected = 0
|
||||
}
|
||||
}
|
||||
val comparator = object : Comparator<AppInfo> {
|
||||
override fun compare(p1: AppInfo, p2: AppInfo): Int = when {
|
||||
val comparator = Comparator<AppInfo> { p1, p2 ->
|
||||
when {
|
||||
p1.isSelected > p2.isSelected -> -1
|
||||
p1.isSelected == p2.isSelected -> 0
|
||||
else -> 1
|
||||
@@ -91,20 +93,20 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
.subscribe {
|
||||
appsAll = it
|
||||
adapter = PerAppProxyAdapter(this, it, blacklist)
|
||||
recycler_view.adapter = adapter
|
||||
pb_waiting.visibility = View.GONE
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.pbWaiting.visibility = View.GONE
|
||||
}
|
||||
|
||||
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
var dst = 0
|
||||
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
dst += dy
|
||||
if (dst > threshold) {
|
||||
header_view.hide()
|
||||
binding.headerView.hide()
|
||||
dst = 0
|
||||
} else if (dst < -20) {
|
||||
header_view.show()
|
||||
binding.headerView.show()
|
||||
dst = 0
|
||||
}
|
||||
}
|
||||
@@ -140,23 +142,23 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
})
|
||||
|
||||
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked)
|
||||
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
|
||||
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
|
||||
}
|
||||
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)
|
||||
binding.switchPerAppProxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||
|
||||
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
|
||||
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked)
|
||||
binding.switchBypassApps.setOnCheckedChangeListener { _, isChecked ->
|
||||
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
|
||||
}
|
||||
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false)
|
||||
binding.switchBypassApps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
|
||||
|
||||
et_search.setOnEditorActionListener { v, actionId, event ->
|
||||
binding.etSearch.setOnEditorActionListener { v, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
//hide
|
||||
var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
|
||||
|
||||
val key = v.text.toString().toUpperCase()
|
||||
val key = v.text.toString().uppercase()
|
||||
val apps = ArrayList<AppInfo>()
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
appsAll?.forEach {
|
||||
@@ -164,13 +166,13 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
} else {
|
||||
appsAll?.forEach {
|
||||
if (it.appName.toUpperCase().indexOf(key) >= 0) {
|
||||
if (it.appName.uppercase().indexOf(key) >= 0) {
|
||||
apps.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
|
||||
recycler_view.adapter = adapter
|
||||
binding.recyclerView.adapter = adapter
|
||||
adapter?.notifyDataSetChanged()
|
||||
true
|
||||
} else {
|
||||
@@ -182,11 +184,11 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
adapter?.let {
|
||||
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist)
|
||||
defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_bypass_list, menu)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
@@ -220,10 +222,15 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
private fun selectProxyApp() {
|
||||
toast(R.string.msg_downloading_content)
|
||||
val url = AppConfig.androidpackagenamelistUrl
|
||||
doAsync {
|
||||
val content = URL(url).readText()
|
||||
uiThread {
|
||||
Log.d("selectProxyApp", content)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val content = try {
|
||||
URL(url).readText()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
Log.d(ANG_PACKAGE, content)
|
||||
selectProxyApp(content)
|
||||
toast(R.string.toast_success)
|
||||
}
|
||||
@@ -243,11 +250,11 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
adapter?.blacklist!!.clear()
|
||||
|
||||
if (switch_bypass_apps.isChecked) {
|
||||
if (binding.switchBypassApps.isChecked) {
|
||||
adapter?.let {
|
||||
it.apps.forEach block@{
|
||||
val packageName = it.packageName
|
||||
Log.d("selectProxyApp2", packageName)
|
||||
Log.d(ANG_PACKAGE, packageName)
|
||||
if (proxyApps.indexOf(packageName) < 0) {
|
||||
adapter?.blacklist!!.add(packageName)
|
||||
println(packageName)
|
||||
@@ -260,7 +267,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
adapter?.let {
|
||||
it.apps.forEach block@{
|
||||
val packageName = it.packageName
|
||||
Log.d("selectProxyApp3", packageName)
|
||||
Log.d(ANG_PACKAGE, packageName)
|
||||
if (proxyApps.indexOf(packageName) >= 0) {
|
||||
adapter?.blacklist!!.add(packageName)
|
||||
println(packageName)
|
||||
@@ -276,4 +283,4 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.graphics.Color
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.*
|
||||
import org.jetbrains.anko.image
|
||||
import org.jetbrains.anko.layoutInflater
|
||||
import org.jetbrains.anko.textColor
|
||||
import java.util.*
|
||||
|
||||
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
||||
@@ -20,8 +18,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
private const val VIEW_TYPE_ITEM = 1
|
||||
}
|
||||
|
||||
private var mActivity: BaseActivity = activity
|
||||
val blacklist = if (blacklist == null) HashSet<String>() else HashSet<String>(blacklist)
|
||||
val blacklist = if (blacklist == null) HashSet() else HashSet(blacklist)
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is AppViewHolder) {
|
||||
@@ -45,8 +42,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
||||
// .inflate(R.layout.item_recycler_bypass_list, parent, false))
|
||||
|
||||
else -> AppViewHolder(ctx.layoutInflater
|
||||
.inflate(R.layout.item_recycler_bypass_list, parent, false))
|
||||
else -> AppViewHolder(ItemRecyclerBypassListBinding.inflate(LayoutInflater.from(ctx), parent, false))
|
||||
|
||||
}
|
||||
}
|
||||
@@ -55,30 +51,25 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
inner class AppViewHolder(itemView: View) : BaseViewHolder(itemView),
|
||||
inner class AppViewHolder(private val itemBypassBinding: ItemRecyclerBypassListBinding) : BaseViewHolder(itemBypassBinding.root),
|
||||
View.OnClickListener {
|
||||
private val inBlacklist: Boolean get() = blacklist.contains(appInfo.packageName)
|
||||
private lateinit var appInfo: AppInfo
|
||||
|
||||
val icon = itemView.icon!!
|
||||
val name = itemView.name!!
|
||||
val package_name = itemView.package_name!!
|
||||
val checkBox = itemView.check_box!!
|
||||
|
||||
fun bind(appInfo: AppInfo) {
|
||||
this.appInfo = appInfo
|
||||
|
||||
icon.image = appInfo.appIcon
|
||||
itemBypassBinding.icon.setImageDrawable(appInfo.appIcon)
|
||||
// name.text = appInfo.appName
|
||||
|
||||
checkBox.isChecked = inBlacklist
|
||||
package_name.text = appInfo.packageName
|
||||
itemBypassBinding.checkBox.isChecked = inBlacklist
|
||||
itemBypassBinding.packageName.text = appInfo.packageName
|
||||
if (appInfo.isSystemApp) {
|
||||
name.text = String.format("** %1s", appInfo.appName)
|
||||
name.textColor = Color.RED
|
||||
itemBypassBinding.name.text = String.format("** %1s", appInfo.appName)
|
||||
itemBypassBinding.name.setTextColor(Color.RED)
|
||||
} else {
|
||||
name.text = appInfo.appName
|
||||
name.textColor = Color.DKGRAY
|
||||
itemBypassBinding.name.text = appInfo.appName
|
||||
itemBypassBinding.name.setTextColor(Color.DKGRAY)
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(this)
|
||||
@@ -87,11 +78,11 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
override fun onClick(v: View?) {
|
||||
if (inBlacklist) {
|
||||
blacklist.remove(appInfo.packageName)
|
||||
checkBox.isChecked = false
|
||||
itemBypassBinding.checkBox.isChecked = false
|
||||
} else {
|
||||
blacklist.add(appInfo.packageName)
|
||||
checkBox.isChecked = true
|
||||
itemBypassBinding.checkBox.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,23 @@ package com.v2ray.ang.ui
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import com.v2ray.ang.R
|
||||
import android.support.v4.app.Fragment
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.v2ray.ang.AppConfig
|
||||
import kotlinx.android.synthetic.main.activity_routing_settings.*
|
||||
|
||||
import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding
|
||||
|
||||
class RoutingSettingsActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityRoutingSettingsBinding
|
||||
|
||||
private val titles: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.routing_tag)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_routing_settings)
|
||||
binding = ActivityRoutingSettingsBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
title = getString(R.string.routing_settings_title)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
@@ -25,9 +29,11 @@ class RoutingSettingsActivity : BaseActivity() {
|
||||
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_DIRECT))
|
||||
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_BLOCKED))
|
||||
|
||||
val adapter = FragmentAdapter(supportFragmentManager, fragments, titles.toList())
|
||||
viewpager?.adapter = adapter
|
||||
tablayout.setTabTextColors(Color.BLACK, Color.RED)
|
||||
tablayout.setupWithViewPager(viewpager)
|
||||
val adapter = FragmentAdapter(this, fragments)
|
||||
binding.viewpager.adapter = adapter
|
||||
binding.tablayout.setTabTextColors(Color.BLACK, Color.RED)
|
||||
TabLayoutMediator(binding.tablayout, binding.viewpager) { tab, position ->
|
||||
tab.text = titles[position]
|
||||
}.attach()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.view.*
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.fragment_routing_settings.*
|
||||
import org.jetbrains.anko.toast
|
||||
import android.view.MenuInflater
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.AppConfig
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.startActivityForResult
|
||||
import org.jetbrains.anko.support.v4.startActivityForResult
|
||||
import org.jetbrains.anko.support.v4.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
|
||||
class RoutingSettingsFragment : Fragment() {
|
||||
private lateinit var binding: FragmentRoutingSettingsBinding
|
||||
companion object {
|
||||
private const val routing_arg = "routing_arg"
|
||||
private const val REQUEST_SCAN_REPLACE = 11
|
||||
private const val REQUEST_SCAN_APPEND = 12
|
||||
}
|
||||
|
||||
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) }
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? {
|
||||
// Inflate the layout for this fragment
|
||||
return inflater.inflate(R.layout.fragment_routing_settings, container, false)
|
||||
binding = FragmentRoutingSettingsBinding.inflate(layoutInflater)
|
||||
return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false)
|
||||
}
|
||||
|
||||
fun newInstance(arg: String): Fragment {
|
||||
@@ -46,11 +45,11 @@ class RoutingSettingsFragment : Fragment() {
|
||||
return fragment
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "")
|
||||
et_routing_content.text = Utils.getEditable(content!!)
|
||||
val content = defaultSharedPreferences.getString(requireArguments().getString(routing_arg), "")
|
||||
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
||||
|
||||
setHasOptionsMenu(true)
|
||||
}
|
||||
@@ -62,21 +61,21 @@ class RoutingSettingsFragment : Fragment() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.save_routing -> {
|
||||
val content = et_routing_content.text.toString()
|
||||
activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content)
|
||||
val content = binding.etRoutingContent.text.toString()
|
||||
defaultSharedPreferences.edit().putString(requireArguments().getString(routing_arg), content).apply()
|
||||
activity?.toast(R.string.toast_success)
|
||||
true
|
||||
}
|
||||
R.id.del_routing -> {
|
||||
et_routing_content.text = null
|
||||
binding.etRoutingContent.text = null
|
||||
true
|
||||
}
|
||||
R.id.scan_replace -> {
|
||||
scanQRcode(REQUEST_SCAN_REPLACE)
|
||||
scanQRcode(true)
|
||||
true
|
||||
}
|
||||
R.id.scan_append -> {
|
||||
scanQRcode(REQUEST_SCAN_APPEND)
|
||||
scanQRcode(false)
|
||||
true
|
||||
}
|
||||
R.id.default_rules -> {
|
||||
@@ -86,17 +85,26 @@ class RoutingSettingsFragment : Fragment() {
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
fun scanQRcode(requestCode: Int): Boolean {
|
||||
private fun saveRouting() {
|
||||
val content = binding.etRoutingContent.text.toString()
|
||||
defaultSharedPreferences.edit().putString(requireArguments().getString(routing_arg), content).apply()
|
||||
activity?.toast(R.string.toast_success)
|
||||
}
|
||||
|
||||
fun scanQRcode(forReplace: Boolean): Boolean {
|
||||
// try {
|
||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
||||
// .addCategory(Intent.CATEGORY_DEFAULT)
|
||||
// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode)
|
||||
// } catch (e: Exception) {
|
||||
RxPermissions(activity!!)
|
||||
RxPermissions(requireActivity())
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
startActivityForResult<ScannerActivity>(requestCode)
|
||||
if (forReplace)
|
||||
scanQRCodeForReplace.launch(Intent(activity, ScannerActivity::class.java))
|
||||
else
|
||||
scanQRCodeForAppend.launch(Intent(activity, ScannerActivity::class.java))
|
||||
else
|
||||
activity?.toast(R.string.toast_permission_denied)
|
||||
}
|
||||
@@ -104,9 +112,23 @@ class RoutingSettingsFragment : Fragment() {
|
||||
return true
|
||||
}
|
||||
|
||||
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
||||
}
|
||||
}
|
||||
|
||||
private val scanQRCodeForAppend = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||
binding.etRoutingContent.text = Utils.getEditable("${binding.etRoutingContent.text},$content")
|
||||
}
|
||||
}
|
||||
|
||||
fun setDefaultRules(): Boolean {
|
||||
var url = AppConfig.v2rayCustomRoutingListUrl
|
||||
when (arguments!!.getString(routing_arg)) {
|
||||
when (requireArguments().getString(routing_arg)) {
|
||||
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
||||
url += AppConfig.TAG_AGENT
|
||||
}
|
||||
@@ -118,32 +140,25 @@ class RoutingSettingsFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
toast(R.string.msg_downloading_content)
|
||||
doAsync {
|
||||
val content = URL(url).readText()
|
||||
uiThread {
|
||||
et_routing_content.text = Utils.getEditable(content!!)
|
||||
toast(R.string.toast_success)
|
||||
activity?.toast(R.string.msg_downloading_content)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val content = try {
|
||||
URL(url).readText()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
val routingList = if (TextUtils.isEmpty(content)) {
|
||||
Utils.readTextFromAssets(activity?.v2RayApplication!!, "custom_routing_$tag")
|
||||
} else {
|
||||
content
|
||||
}
|
||||
binding.etRoutingContent.text = Utils.getEditable(routingList)
|
||||
saveRouting()
|
||||
//toast(R.string.toast_success)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
REQUEST_SCAN_REPLACE ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
val content = data?.getStringExtra("SCAN_RESULT")
|
||||
et_routing_content.text = Utils.getEditable(content!!)
|
||||
}
|
||||
REQUEST_SCAN_APPEND ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
val content = data?.getStringExtra("SCAN_RESULT")
|
||||
et_routing_content.text = Utils.getEditable("${et_routing_content.text},$content")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -6,25 +6,23 @@ import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import android.os.Bundle
|
||||
import org.jetbrains.anko.*
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.v2ray.ang.extension.toast
|
||||
|
||||
class ScScannerActivity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_none)
|
||||
importQRcode(REQUEST_SCAN)
|
||||
importQRcode()
|
||||
}
|
||||
|
||||
fun importQRcode(requestCode: Int): Boolean {
|
||||
fun importQRcode(): Boolean {
|
||||
RxPermissions(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
startActivityForResult<ScannerActivity>(requestCode)
|
||||
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
|
||||
else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
@@ -32,21 +30,16 @@ class ScScannerActivity : BaseActivity() {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
REQUEST_SCAN ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
val count = AngConfigManager.importBatchConfig(data?.getStringExtra("SCAN_RESULT"), "")
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
startActivity<MainActivity>()
|
||||
}
|
||||
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
val count = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "")
|
||||
if (count > 0) {
|
||||
toast(R.string.toast_success)
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,34 +7,21 @@ import com.google.zxing.Result
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.icu.util.TimeUnit
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import org.jetbrains.anko.toast
|
||||
import rx.Observable
|
||||
import android.os.SystemClock
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import rx.Observer
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import javax.xml.datatype.DatatypeConstants.SECONDS
|
||||
|
||||
|
||||
|
||||
|
||||
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
companion object {
|
||||
private const val REQUEST_FILE_CHOOSER = 2
|
||||
}
|
||||
|
||||
|
||||
private var mScannerView: ZXingScannerView? = null
|
||||
|
||||
public override fun onCreate(state: Bundle?) {
|
||||
super.onCreate(state)
|
||||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
|
||||
|
||||
mScannerView?.setAutoFocus(true)
|
||||
@@ -69,14 +56,14 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
// mScannerView!!.resumeCameraPreview(this)
|
||||
}
|
||||
|
||||
fun finished(text: String) {
|
||||
private fun finished(text: String) {
|
||||
val intent = Intent()
|
||||
intent.putExtra("SCAN_RESULT", text)
|
||||
setResult(Activity.RESULT_OK, intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_scanner, menu)
|
||||
return true
|
||||
}
|
||||
@@ -107,30 +94,22 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
//intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
|
||||
|
||||
try {
|
||||
startActivityForResult(
|
||||
Intent.createChooser(intent, getString(R.string.title_file_chooser)),
|
||||
REQUEST_FILE_CHOOSER)
|
||||
chooseFile.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
||||
} catch (ex: android.content.ActivityNotFoundException) {
|
||||
toast(R.string.toast_require_file_manager)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
REQUEST_FILE_CHOOSER -> {
|
||||
val uri = data?.data
|
||||
if (resultCode == RESULT_OK && uri != null) {
|
||||
try {
|
||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||
finished(text)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
toast(e.message.toString())
|
||||
}
|
||||
}
|
||||
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
val uri = it.data?.data
|
||||
if (it.resultCode == RESULT_OK && uri != null) {
|
||||
try {
|
||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||
finished(text)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
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,121 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.*
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.ServerConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_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 kotlinx.android.synthetic.main.activity_server.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
|
||||
private val isRunning by lazy {
|
||||
intent.getBooleanExtra("isRunning", false)
|
||||
&& editGuid.isNotEmpty()
|
||||
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
|
||||
}
|
||||
private val createConfigType by lazy {
|
||||
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
|
||||
}
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的服务器
|
||||
private var edit_guid: String = ""
|
||||
private var isRunning: Boolean = false
|
||||
private val securitys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.securitys)
|
||||
}
|
||||
private val shadowsocksSecuritys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.ss_securitys)
|
||||
}
|
||||
private val flows: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.flows)
|
||||
}
|
||||
private val networks: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.networks)
|
||||
}
|
||||
private val headertypes: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.headertypes)
|
||||
private val tcpTypes: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.header_type_tcp)
|
||||
}
|
||||
private val streamsecuritys: Array<out String> by lazy {
|
||||
resources.getStringArray(R.array.streamsecuritys)
|
||||
private val kcpAndQuicTypes: Array<out String> by lazy {
|
||||
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)
|
||||
}
|
||||
|
||||
// Kotlin synthetics was used, but since it is removed in 1.8. We switch to old manual approach.
|
||||
// We don't use AndroidViewBinding because, it is better to share similar logics for different
|
||||
// protocols. Use findViewById manually ensures the xml are de-coupled with the activity logic.
|
||||
private val et_remarks: EditText by lazy { findViewById(R.id.et_remarks) }
|
||||
private val et_address: EditText by lazy { findViewById(R.id.et_address) }
|
||||
private val et_port: EditText by lazy { findViewById(R.id.et_port) }
|
||||
private val et_id: EditText by lazy { findViewById(R.id.et_id) }
|
||||
//private val et_alterId: EditText? by lazy { findViewById(R.id.et_alterId) }
|
||||
private val et_security: EditText? by lazy { findViewById(R.id.et_security) }
|
||||
//private val sp_flow: Spinner? by lazy { findViewById(R.id.sp_flow) }
|
||||
private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) }
|
||||
private val sp_stream_security: Spinner? by lazy { findViewById(R.id.sp_stream_security) }
|
||||
private val sp_allow_insecure: Spinner? by lazy { findViewById(R.id.sp_allow_insecure) }
|
||||
//private val et_sni: EditText? by lazy { findViewById(R.id.et_sni) }
|
||||
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
|
||||
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
|
||||
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
|
||||
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
|
||||
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_server)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
isRunning = intent.getBooleanExtra("isRunning", false)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
edit_guid = configs.vmess[edit_index].guid
|
||||
bindingServer(configs.vmess[edit_index])
|
||||
val config = MmkvManager.decodeServerConfig(editGuid)
|
||||
when(config?.configType ?: createConfigType) {
|
||||
EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
|
||||
EConfigType.CUSTOM -> return
|
||||
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
||||
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||
// EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||
// EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||
}
|
||||
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 {
|
||||
clearServer()
|
||||
}
|
||||
@@ -58,33 +125,43 @@ class ServerActivity : BaseActivity() {
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
|
||||
et_remarks.text = Utils.getEditable(vmess.remarks)
|
||||
private fun bindingServer(config: ServerConfig): Boolean {
|
||||
val outbound = config.getProxyOutbound() ?: return false
|
||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||
|
||||
et_address.text = Utils.getEditable(vmess.address)
|
||||
et_port.text = Utils.getEditable(vmess.port.toString())
|
||||
et_id.text = Utils.getEditable(vmess.id)
|
||||
et_alterId.text = Utils.getEditable(vmess.alterId.toString())
|
||||
|
||||
val security = Utils.arrayFind(securitys, vmess.security)
|
||||
et_remarks.text = Utils.getEditable(config.remarks)
|
||||
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
|
||||
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
|
||||
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
|
||||
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) {
|
||||
sp_security.setSelection(security)
|
||||
}
|
||||
val network = Utils.arrayFind(networks, vmess.network)
|
||||
if (network >= 0) {
|
||||
sp_network.setSelection(network)
|
||||
sp_security?.setSelection(security)
|
||||
}
|
||||
|
||||
val headerType = Utils.arrayFind(headertypes, vmess.headerType)
|
||||
if (headerType >= 0) {
|
||||
sp_header_type.setSelection(headerType)
|
||||
}
|
||||
et_request_host.text = Utils.getEditable(vmess.requestHost)
|
||||
et_path.text = Utils.getEditable(vmess.path)
|
||||
|
||||
val streamSecurity = Utils.arrayFind(streamsecuritys, vmess.streamSecurity)
|
||||
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
|
||||
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
|
||||
}
|
||||
@@ -92,113 +169,172 @@ class ServerActivity : BaseActivity() {
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
private fun clearServer(): Boolean {
|
||||
et_remarks.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_alterId.text = Utils.getEditable("64")
|
||||
sp_security.setSelection(0)
|
||||
sp_network.setSelection(0)
|
||||
sp_security?.setSelection(0)
|
||||
sp_network?.setSelection(0)
|
||||
|
||||
sp_header_type.setSelection(0)
|
||||
et_request_host.text = null
|
||||
et_path.text = null
|
||||
sp_stream_security.setSelection(0)
|
||||
sp_header_type?.setSelection(0)
|
||||
et_request_host?.text = null
|
||||
et_path?.text = null
|
||||
sp_stream_security?.setSelection(0)
|
||||
sp_allow_insecure?.setSelection(0)
|
||||
|
||||
//et_security.text = null
|
||||
//sp_flow?.setSelection(0)
|
||||
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.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)) {
|
||||
private fun saveServer(): Boolean {
|
||||
if (TextUtils.isEmpty(et_remarks.text.toString())) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.address)) {
|
||||
if (TextUtils.isEmpty(et_address.text.toString())) {
|
||||
toast(R.string.server_lab_address)
|
||||
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)
|
||||
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)
|
||||
return false
|
||||
}
|
||||
if (TextUtils.isEmpty(vmess.alterId.toString()) || vmess.alterId < 0) {
|
||||
toast(R.string.server_lab_alterid)
|
||||
return false
|
||||
|
||||
config.remarks = et_remarks.text.toString().trim()
|
||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||
saveVnext(vnext, port, config)
|
||||
}
|
||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||
saveServers(server, port, config)
|
||||
}
|
||||
config.outboundBean?.streamSettings?.let {
|
||||
saveStreamSettings(it)
|
||||
}
|
||||
|
||||
if (AngConfigManager.addServer(vmess, edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
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].security = securitys[sp_security?.selectedItemPosition ?: 0]
|
||||
} else if (config.configType == EConfigType.VLESS) {
|
||||
vnext.users[0].encryption = et_security?.text.toString().trim()
|
||||
if (streamSecuritys[sp_stream_security?.selectedItemPosition ?: 0] == XTLS) {
|
||||
// vnext.users[0].flow = flows[sp_flow.selectedItemPosition].ifBlank { V2rayConfig.DEFAULT_FLOW }
|
||||
} 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 ?: 0]
|
||||
} 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) {
|
||||
val network = sp_network?.selectedItemPosition ?: return
|
||||
val type = sp_header_type?.selectedItemPosition ?: return
|
||||
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
|
||||
val path = et_path?.text?.toString()?.trim() ?: return
|
||||
//val sniField = et_sni?.text?.toString()?.trim() ?: return
|
||||
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
|
||||
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
|
||||
|
||||
var sni = streamSetting.populateTransportSettings(
|
||||
transport = networks[network],
|
||||
headerType = transportTypes(networks[network])[type],
|
||||
host = requestHost,
|
||||
path = path,
|
||||
seed = path,
|
||||
quicSecurity = requestHost,
|
||||
key = path,
|
||||
mode = transportTypes(networks[network])[type],
|
||||
serviceName = path
|
||||
)
|
||||
//if (sniField.isNotBlank()) {
|
||||
// sni = sniField
|
||||
//}
|
||||
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {
|
||||
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
allowinsecures[allowInsecureField].toBoolean()
|
||||
}
|
||||
streamSetting.populateTlsSettings(streamSecuritys[streamSecurity], 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
|
||||
*/
|
||||
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)
|
||||
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()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
} else {
|
||||
.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
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)
|
||||
val delButton = menu.findItem(R.id.del_config)
|
||||
val saveButton = menu.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
if (editGuid.isNotEmpty()) {
|
||||
if (isRunning) {
|
||||
if (edit_index == configs.index) {
|
||||
del_config?.isVisible = false
|
||||
save_config?.isVisible = false
|
||||
}
|
||||
delButton?.isVisible = false
|
||||
saveButton?.isVisible = false
|
||||
}
|
||||
} else {
|
||||
del_config?.isVisible = false
|
||||
delButton?.isVisible = false
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
@@ -215,4 +351,4 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||
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 me.drakeet.support.toast.ToastCompat
|
||||
|
||||
class ServerCustomConfigActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityServerCustomConfigBinding
|
||||
|
||||
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)
|
||||
binding = ActivityServerCustomConfigBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
title = getString(R.string.title_server)
|
||||
|
||||
binding.editor.language = JsonLanguage()
|
||||
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 {
|
||||
binding.etRemarks.text = Utils.getEditable(config.remarks)
|
||||
val raw = serverRawStorage?.decodeString(editGuid)
|
||||
if (raw.isNullOrBlank()) {
|
||||
binding.editor.setTextContent(Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty()))
|
||||
} else {
|
||||
binding.editor.setTextContent(Utils.getEditable(raw))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
private fun clearServer(): Boolean {
|
||||
binding.etRemarks.text = null
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
private fun saveServer(): Boolean {
|
||||
if (TextUtils.isEmpty(binding.etRemarks.text.toString())) {
|
||||
toast(R.string.server_lab_remarks)
|
||||
return false
|
||||
}
|
||||
|
||||
val v2rayConfig = try {
|
||||
Gson().fromJson(binding.editor.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 = binding.etRemarks.text.toString().trim()
|
||||
config.fullConfig = v2rayConfig
|
||||
|
||||
MmkvManager.encodeServerConfig(editGuid, config)
|
||||
serverRawStorage?.encode(editGuid, binding.editor.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,41 +1,20 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v7.preference.*
|
||||
import androidx.preference.*
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.jetbrains.anko.defaultSharedPreferences
|
||||
import org.jetbrains.anko.startActivity
|
||||
import org.jetbrains.anko.toast
|
||||
import com.v2ray.ang.viewmodel.SettingsViewModel
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
companion object {
|
||||
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
||||
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
|
||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
||||
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
|
||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
|
||||
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
|
||||
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
|
||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||
|
||||
// const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||
// const val PREF_HTTP_PORT = "pref_http_port"
|
||||
|
||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
|
||||
// const val PREF_DONATE = "pref_donate"
|
||||
// const val PREF_LICENSES = "pref_licenses"
|
||||
// const val PREF_FEEDBACK = "pref_feedback"
|
||||
// const val PREF_TG_GROUP = "pref_tg_group"
|
||||
// const val PREF_AUTO_RESTART = "pref_auto_restart"
|
||||
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
||||
}
|
||||
private val settingsViewModel: SettingsViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -44,20 +23,26 @@ class SettingsActivity : BaseActivity() {
|
||||
title = getString(R.string.title_settings)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
settingsViewModel.startListenPreferenceChange()
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
private val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||
private val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||
private val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||
private val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||
private val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||
private val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference }
|
||||
private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||
private val localDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
||||
private val fakeDns by lazy { findPreference(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
||||
private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||
private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) }
|
||||
private val sppedEnabled by lazy { findPreference(AppConfig.PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||
private val sniffingEnabled by lazy { findPreference(AppConfig.PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||
private val proxySharing by lazy { findPreference(AppConfig.PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||
private val domainStrategy by lazy { findPreference(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||
private val routingMode by lazy { findPreference(AppConfig.PREF_ROUTING_MODE) as ListPreference }
|
||||
|
||||
private val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||
private val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||
private val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||
private val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
|
||||
private val forwardIpv6 by lazy { findPreference(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||
private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||
private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||
private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
|
||||
|
||||
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
||||
|
||||
@@ -65,7 +50,7 @@ class SettingsActivity : BaseActivity() {
|
||||
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
|
||||
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
|
||||
|
||||
private val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) }
|
||||
private val routingCustom: Preference by lazy { findPreference(AppConfig.PREF_ROUTING_CUSTOM) }
|
||||
// val donate: Preference by lazy { findPreference(PREF_DONATE) }
|
||||
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
||||
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
||||
@@ -75,7 +60,7 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
private fun restartProxy() {
|
||||
Utils.stopVService(requireContext())
|
||||
Utils.startVService(requireContext())
|
||||
V2RayServiceManager.startV2Ray(requireContext())
|
||||
}
|
||||
|
||||
private fun isRunning(): Boolean {
|
||||
@@ -89,7 +74,7 @@ class SettingsActivity : BaseActivity() {
|
||||
if (isRunning()) {
|
||||
Utils.stopVService(requireContext())
|
||||
}
|
||||
activity?.startActivity<PerAppProxyActivity>()
|
||||
startActivity(Intent(activity, PerAppProxyActivity::class.java))
|
||||
perAppProxy.isChecked = true
|
||||
true
|
||||
}
|
||||
@@ -126,7 +111,7 @@ class SettingsActivity : BaseActivity() {
|
||||
routingCustom.setOnPreferenceClickListener {
|
||||
if (isRunning())
|
||||
Utils.stopVService(requireContext())
|
||||
activity?.startActivity<RoutingSettingsActivity>()
|
||||
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
|
||||
false
|
||||
}
|
||||
|
||||
@@ -160,9 +145,21 @@ class SettingsActivity : BaseActivity() {
|
||||
restartProxy()
|
||||
true
|
||||
}
|
||||
|
||||
localDns?.setOnPreferenceChangeListener{ _, any ->
|
||||
updateLocalDns(any as Boolean)
|
||||
true
|
||||
}
|
||||
localDnsPort?.setOnPreferenceChangeListener { _, any ->
|
||||
val nval = any as String
|
||||
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) "10807" else nval
|
||||
true
|
||||
}
|
||||
vpnDns?.setOnPreferenceChangeListener { _, any ->
|
||||
vpnDns?.summary = any as String
|
||||
true
|
||||
}
|
||||
mode.setOnPreferenceChangeListener { _, newValue ->
|
||||
updatePerAppProxy(newValue.toString())
|
||||
updateMode(newValue.toString())
|
||||
true
|
||||
}
|
||||
mode.dialogLayoutResource = R.layout.preference_with_help_link
|
||||
@@ -206,32 +203,44 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
updatePerAppProxy(activity?.defaultSharedPreferences?.getString(AppConfig.PREF_MODE, "VPN"))
|
||||
remoteDns.summary = activity?.defaultSharedPreferences?.getString(PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = activity?.defaultSharedPreferences?.getString(PREF_DOMESTIC_DNS, "")
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
|
||||
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
|
||||
|
||||
if (remoteDns.summary == "") {
|
||||
remoteDns.summary = AppConfig.DNS_AGENT
|
||||
if (TextUtils.isEmpty(remoteDnsString)) {
|
||||
remoteDnsString = AppConfig.DNS_AGENT
|
||||
}
|
||||
|
||||
if ( domesticDns.summary == "") {
|
||||
domesticDns.summary = AppConfig.DNS_DIRECT
|
||||
}
|
||||
remoteDns.summary = remoteDnsString
|
||||
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
|
||||
|
||||
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
|
||||
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
|
||||
}
|
||||
|
||||
private fun updatePerAppProxy(mode: String?) {
|
||||
val preference = activity?.defaultSharedPreferences ?: return
|
||||
if (mode == "VPN") {
|
||||
perAppProxy.isEnabled = true
|
||||
perAppProxy.isChecked = preference.getBoolean(PREF_PER_APP_PROXY, false)
|
||||
} else {
|
||||
perAppProxy.isEnabled = false
|
||||
perAppProxy.isChecked = false
|
||||
private fun updateMode(mode: String?) {
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
val vpn = mode == "VPN"
|
||||
perAppProxy.isEnabled = vpn
|
||||
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||
localDns?.isEnabled = vpn
|
||||
fakeDns?.isEnabled = vpn
|
||||
localDnsPort?.isEnabled = vpn
|
||||
vpnDns?.isEnabled = vpn
|
||||
if (vpn) {
|
||||
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false))
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLocalDns(enabled: Boolean) {
|
||||
fakeDns?.isEnabled = enabled
|
||||
localDnsPort?.isEnabled = enabled
|
||||
vpnDns?.isEnabled = !enabled
|
||||
}
|
||||
}
|
||||
|
||||
fun onModeHelpClicked(view: View) {
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.gson.Gson
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.databinding.ActivitySubEditBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_sub_edit.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
|
||||
class SubEditActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubEditBinding
|
||||
|
||||
var del_config: MenuItem? = null
|
||||
var save_config: MenuItem? = null
|
||||
|
||||
private lateinit var configs: AngConfig
|
||||
private var edit_index: Int = -1 //当前编辑的
|
||||
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val editSubId by lazy { intent.getStringExtra("subId").orEmpty() }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_sub_edit)
|
||||
|
||||
configs = AngConfigManager.configs
|
||||
edit_index = intent.getIntExtra("position", -1)
|
||||
|
||||
binding = ActivitySubEditBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
title = getString(R.string.title_sub_setting)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
bindingServer(configs.subItem[edit_index])
|
||||
val json = subStorage?.decodeString(editSubId)
|
||||
if (!json.isNullOrBlank()) {
|
||||
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
|
||||
} else {
|
||||
clearServer()
|
||||
}
|
||||
@@ -40,9 +42,9 @@ class SubEditActivity : BaseActivity() {
|
||||
/**
|
||||
* bingding seleced server config
|
||||
*/
|
||||
fun bindingServer(subItem: AngConfig.SubItemBean): Boolean {
|
||||
et_remarks.text = Utils.getEditable(subItem.remarks)
|
||||
et_url.text = Utils.getEditable(subItem.url)
|
||||
private fun bindingServer(subItem: SubscriptionItem): Boolean {
|
||||
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
|
||||
binding.etUrl.text = Utils.getEditable(subItem.url)
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -50,9 +52,9 @@ class SubEditActivity : BaseActivity() {
|
||||
/**
|
||||
* clear or init server config
|
||||
*/
|
||||
fun clearServer(): Boolean {
|
||||
et_remarks.text = null
|
||||
et_url.text = null
|
||||
private fun clearServer(): Boolean {
|
||||
binding.etRemarks.text = null
|
||||
binding.etUrl.text = null
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -60,16 +62,19 @@ class SubEditActivity : BaseActivity() {
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun saveServer(): Boolean {
|
||||
val subItem: AngConfig.SubItemBean
|
||||
if (edit_index >= 0) {
|
||||
subItem = configs.subItem[edit_index]
|
||||
private fun saveServer(): Boolean {
|
||||
val subItem: SubscriptionItem
|
||||
val json = subStorage?.decodeString(editSubId)
|
||||
var subId = editSubId
|
||||
if (!json.isNullOrBlank()) {
|
||||
subItem = Gson().fromJson(json, SubscriptionItem::class.java)
|
||||
} else {
|
||||
subItem = AngConfig.SubItemBean()
|
||||
subId = Utils.getUuid()
|
||||
subItem = SubscriptionItem()
|
||||
}
|
||||
|
||||
subItem.remarks = et_remarks.text.toString()
|
||||
subItem.url = et_url.text.toString()
|
||||
subItem.remarks = binding.etRemarks.text.toString()
|
||||
subItem.url = binding.etUrl.text.toString()
|
||||
|
||||
if (TextUtils.isEmpty(subItem.remarks)) {
|
||||
toast(R.string.sub_setting_remarks)
|
||||
@@ -80,44 +85,33 @@ class SubEditActivity : BaseActivity() {
|
||||
return false
|
||||
}
|
||||
|
||||
if (AngConfigManager.addSubItem(subItem, edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
return false
|
||||
}
|
||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* save server config
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
alert(R.string.del_config_comfirm) {
|
||||
positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeSubItem(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
private fun deleteServer(): Boolean {
|
||||
if (editSubId.isNotEmpty()) {
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
MmkvManager.removeSubscription(editSubId)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
} else {
|
||||
.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
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)
|
||||
del_config = menu.findItem(R.id.del_config)
|
||||
save_config = menu.findItem(R.id.save_config)
|
||||
|
||||
if (edit_index >= 0) {
|
||||
} else {
|
||||
if (editSubId.isEmpty()) {
|
||||
del_config?.isVisible = false
|
||||
}
|
||||
|
||||
@@ -135,4 +129,4 @@ class SubEditActivity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,55 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import kotlinx.android.synthetic.main.activity_sub_setting.*
|
||||
import android.os.Bundle
|
||||
import org.jetbrains.anko.startActivity
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||
import com.v2ray.ang.dto.SubscriptionItem
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
class SubSettingActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySubSettingBinding
|
||||
|
||||
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
|
||||
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_sub_setting)
|
||||
binding = ActivitySubSettingBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
title = getString(R.string.title_sub_setting)
|
||||
|
||||
recycler_view.setHasFixedSize(true)
|
||||
recycler_view.layoutManager = LinearLayoutManager(this)
|
||||
recycler_view.adapter = adapter
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
adapter.updateConfigList()
|
||||
subscriptions = MmkvManager.decodeSubscriptions()
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
||||
menu?.findItem(R.id.del_config)?.isVisible = false
|
||||
menu?.findItem(R.id.save_config)?.isVisible = false
|
||||
menu.findItem(R.id.del_config)?.isVisible = false
|
||||
menu.findItem(R.id.save_config)?.isVisible = false
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.add_config -> {
|
||||
startActivity<SubEditActivity>("position" to -1)
|
||||
adapter.updateConfigList()
|
||||
startActivity(Intent(this, SubEditActivity::class.java))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,35 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
|
||||
import org.jetbrains.anko.*
|
||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
|
||||
|
||||
private var mActivity: SubSettingActivity = activity
|
||||
private lateinit var configs: AngConfig
|
||||
|
||||
init {
|
||||
updateConfigList()
|
||||
}
|
||||
override fun getItemCount() = mActivity.subscriptions.size
|
||||
|
||||
override fun getItemCount() = configs.subItem.count()
|
||||
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
|
||||
val subId = mActivity.subscriptions[position].first
|
||||
val subItem = mActivity.subscriptions[position].second
|
||||
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
||||
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val remarks = configs.subItem[position].remarks
|
||||
val url = configs.subItem[position].url
|
||||
|
||||
holder.name.text = remarks
|
||||
holder.url.text = url
|
||||
holder.itemView.backgroundColor = Color.TRANSPARENT
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
mActivity.startActivity<SubEditActivity>("position" to position)
|
||||
}
|
||||
} else {
|
||||
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
|
||||
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
|
||||
.putExtra("subId", subId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||
return MainViewHolder(parent.context.layoutInflater
|
||||
.inflate(R.layout.item_recycler_sub_setting, parent, false))
|
||||
}
|
||||
|
||||
fun updateConfigList() {
|
||||
configs = AngConfigManager.configs
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
// fun updateSelectedItem() {
|
||||
// notifyItemChanged(configs.index)
|
||||
// }
|
||||
|
||||
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {
|
||||
val name = itemView.tv_name!!
|
||||
val url = itemView.tv_url!!
|
||||
val layout_edit = itemView.layout_edit!!
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
||||
return MainViewHolder(ItemRecyclerSubSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
|
||||
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) : RecyclerView.ViewHolder(itemSubSettingBinding.root)
|
||||
}
|
||||
|
||||
@@ -7,32 +7,40 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.ListView
|
||||
import java.util.ArrayList
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.zxing.WriterException
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import kotlinx.android.synthetic.main.activity_tasker.*
|
||||
|
||||
import com.v2ray.ang.databinding.ActivityTaskerBinding
|
||||
import com.v2ray.ang.util.MmkvManager
|
||||
|
||||
class TaskerActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivityTaskerBinding
|
||||
|
||||
private var listview: ListView? = null
|
||||
private var lstData: ArrayList<String> = ArrayList()
|
||||
private var lstGuid: ArrayList<String> = ArrayList()
|
||||
|
||||
private val serverStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_tasker)
|
||||
binding = ActivityTaskerBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
//add def value
|
||||
lstData.add("Default")
|
||||
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
|
||||
|
||||
AngConfigManager.configs.vmess.forEach {
|
||||
lstData.add(it.remarks)
|
||||
lstGuid.add(it.guid)
|
||||
serverStorage?.allKeys()?.forEach { key ->
|
||||
MmkvManager.decodeServerConfig(key)?.let { config ->
|
||||
lstData.add(config.remarks)
|
||||
lstGuid.add(key)
|
||||
}
|
||||
}
|
||||
val adapter = ArrayAdapter(this,
|
||||
android.R.layout.simple_list_item_single_choice, lstData)
|
||||
@@ -51,7 +59,7 @@ class TaskerActivity : BaseActivity() {
|
||||
if (switch == null || TextUtils.isEmpty(guid)) {
|
||||
return
|
||||
} else {
|
||||
switch_start_service.isChecked = switch
|
||||
binding.switchStartService.isChecked = switch
|
||||
val pos = lstGuid.indexOf(guid.toString())
|
||||
if (pos >= 0) {
|
||||
listview?.setItemChecked(pos, true)
|
||||
@@ -70,17 +78,15 @@ class TaskerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val extraBundle = Bundle()
|
||||
extraBundle.putBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, switch_start_service.isChecked)
|
||||
extraBundle.putBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, binding.switchStartService.isChecked)
|
||||
extraBundle.putString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, lstGuid[position])
|
||||
val intent = Intent()
|
||||
|
||||
val remarks = lstData[position]
|
||||
var blurb = ""
|
||||
|
||||
if (switch_start_service.isChecked) {
|
||||
blurb = "Start $remarks"
|
||||
val blurb = if (binding.switchStartService.isChecked) {
|
||||
"Start $remarks"
|
||||
} else {
|
||||
blurb = "Stop $remarks"
|
||||
"Stop $remarks"
|
||||
}
|
||||
|
||||
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
|
||||
@@ -89,9 +95,9 @@ class TaskerActivity : BaseActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.action_server, menu)
|
||||
val del_config = menu?.findItem(R.id.del_config)
|
||||
val del_config = menu.findItem(R.id.del_config)
|
||||
del_config?.isVisible = false
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
@@ -108,4 +114,3 @@ class TaskerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,7 @@ object AppManagerUtil {
|
||||
return apps
|
||||
}
|
||||
|
||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create {
|
||||
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
|
||||
it.onNext(loadNetworkAppList(ctx))
|
||||
}
|
||||
|
||||
|
||||
144
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt
Normal file
144
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/MmkvManager.kt
Normal file
@@ -0,0 +1,144 @@
|
||||
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 = guid.ifBlank { Utils.getUuid() }
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -15,30 +15,26 @@ import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.webkit.URLUtil
|
||||
import com.v2ray.ang.AngApplication
|
||||
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.responseLength
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import kotlinx.coroutines.isActive
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.jetbrains.anko.toast
|
||||
import java.io.IOException
|
||||
import java.net.*
|
||||
import libv2ray.Libv2ray
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
|
||||
object Utils {
|
||||
|
||||
val tcpTestingSockets = ArrayList<Socket?>()
|
||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val tcpTestingSockets = ArrayList<Socket?>()
|
||||
|
||||
/**
|
||||
* convert string to editalbe for kotlin
|
||||
@@ -66,11 +62,11 @@ object Utils {
|
||||
* parseInt
|
||||
*/
|
||||
fun parseInt(str: String): Int {
|
||||
try {
|
||||
return Integer.parseInt(str)
|
||||
return try {
|
||||
Integer.parseInt(str)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return 0
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,12 +74,12 @@ object Utils {
|
||||
* get text from clipboard
|
||||
*/
|
||||
fun getClipboard(context: Context): String {
|
||||
try {
|
||||
return try {
|
||||
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
return cmb.primaryClip?.getItemAt(0)?.text.toString()
|
||||
cmb.primaryClip?.getItemAt(0)?.text.toString()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return ""
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,64 +100,68 @@ object Utils {
|
||||
* base64 decode
|
||||
*/
|
||||
fun decode(text: String): String {
|
||||
tryDecodeBase64(text)?.let { return it }
|
||||
if (text.endsWith('=')) {
|
||||
// try again for some loosely formatted base64
|
||||
tryDecodeBase64(text.trimEnd('='))?.let { return it }
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fun tryDecodeBase64(text: String): String? {
|
||||
try {
|
||||
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return ""
|
||||
Log.i(AppConfig.ANG_PACKAGE, "Parse base64 standard failed $e")
|
||||
}
|
||||
try {
|
||||
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8"))
|
||||
} catch (e: Exception) {
|
||||
Log.i(AppConfig.ANG_PACKAGE, "Parse base64 url safe failed $e")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* base64 encode
|
||||
*/
|
||||
fun encode(text: String): String {
|
||||
try {
|
||||
return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
|
||||
return try {
|
||||
Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return ""
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get remote dns servers from preference
|
||||
*/
|
||||
fun getRemoteDnsServers(defaultDPreference: DPreference): ArrayList<String> {
|
||||
val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, AppConfig.DNS_AGENT)
|
||||
val ret = ArrayList<String>()
|
||||
if (!TextUtils.isEmpty(remoteDns)) {
|
||||
remoteDns
|
||||
.split(",")
|
||||
.forEach {
|
||||
if (Utils.isPureIpAddress(it)) {
|
||||
ret.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret.size == 0) {
|
||||
ret.add(AppConfig.DNS_AGENT)
|
||||
fun getRemoteDnsServers(): List<String> {
|
||||
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
|
||||
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||
if (ret.isEmpty()) {
|
||||
return listOf(AppConfig.DNS_AGENT)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
fun getVpnDnsServers(): List<String> {
|
||||
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS)
|
||||
?: settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS)
|
||||
?: AppConfig.DNS_AGENT
|
||||
return vpnDns.split(",").filter { isPureIpAddress(it) }
|
||||
// allow empty, in that case dns will use system default
|
||||
}
|
||||
|
||||
/**
|
||||
* get remote dns servers from preference
|
||||
*/
|
||||
fun getDomesticDnsServers(defaultDPreference: DPreference): ArrayList<String> {
|
||||
val domesticDns = defaultDPreference.getPrefString(SettingsActivity.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
|
||||
val ret = ArrayList<String>()
|
||||
if (!TextUtils.isEmpty(domesticDns)) {
|
||||
domesticDns
|
||||
.split(",")
|
||||
.forEach {
|
||||
if (Utils.isPureIpAddress(it)) {
|
||||
ret.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret.size == 0) {
|
||||
ret.add(AppConfig.DNS_DIRECT)
|
||||
fun getDomesticDnsServers(): List<String> {
|
||||
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
||||
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||
if (ret.isEmpty()) {
|
||||
return listOf(AppConfig.DNS_DIRECT)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -172,12 +172,12 @@ object Utils {
|
||||
fun createQRCode(text: String, size: Int = 800): Bitmap? {
|
||||
try {
|
||||
val hints = HashMap<EncodeHintType, String>()
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8")
|
||||
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||
val bitMatrix = QRCodeWriter().encode(text,
|
||||
BarcodeFormat.QR_CODE, size, size, hints)
|
||||
val pixels = IntArray(size * size)
|
||||
for (y in 0..size - 1) {
|
||||
for (x in 0..size - 1) {
|
||||
for (y in 0 until size) {
|
||||
for (x in 0 until size) {
|
||||
if (bitMatrix.get(x, y)) {
|
||||
pixels[y * size + x] = 0xff000000.toInt()
|
||||
} else {
|
||||
@@ -222,7 +222,7 @@ object Utils {
|
||||
}
|
||||
|
||||
// addr = addr.toLowerCase()
|
||||
var octets = addr.split('.').toTypedArray()
|
||||
val octets = addr.split('.').toTypedArray()
|
||||
if (octets.size == 4) {
|
||||
if(octets[3].indexOf(":") > 0) {
|
||||
addr = addr.substring(0, addr.indexOf(":"))
|
||||
@@ -257,12 +257,16 @@ object Utils {
|
||||
return regV6.matches(addr)
|
||||
}
|
||||
|
||||
private fun isCoreDNSAddress(s: String): Boolean {
|
||||
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic")
|
||||
}
|
||||
|
||||
/**
|
||||
* is valid url
|
||||
*/
|
||||
fun isValidUrl(value: String?): Boolean {
|
||||
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
|
||||
}
|
||||
} catch (e: WriterException) {
|
||||
@@ -273,55 +277,12 @@ object Utils {
|
||||
}
|
||||
|
||||
fun startVServiceFromToggle(context: Context): Boolean {
|
||||
val result = startVService(context)
|
||||
if (!result) {
|
||||
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
|
||||
context.toast(R.string.app_tile_first_use)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 == EConfigType.CUSTOM) {
|
||||
try {
|
||||
Libv2ray.testConfig(configContent)
|
||||
} catch (e: Exception) {
|
||||
context.toast(e.toString())
|
||||
return false
|
||||
}
|
||||
}
|
||||
V2RayServiceManager.startV2Ray(context, context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN"))
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
V2RayServiceManager.startV2Ray(context)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -341,29 +302,29 @@ object Utils {
|
||||
* uuid
|
||||
*/
|
||||
fun getUuid(): String {
|
||||
try {
|
||||
return UUID.randomUUID().toString().replace("-", "")
|
||||
return try {
|
||||
UUID.randomUUID().toString().replace("-", "")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return ""
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fun urlDecode(url: String): String {
|
||||
try {
|
||||
return URLDecoder.decode(url, "UTF-8")
|
||||
return try {
|
||||
URLDecoder.decode(url, "UTF-8")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return url
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
fun urlEncode(url: String): String {
|
||||
try {
|
||||
return URLEncoder.encode(url, "UTF-8")
|
||||
return try {
|
||||
URLEncoder.encode(url, "UTF-8")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return url
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,8 +384,8 @@ object Utils {
|
||||
/**
|
||||
* readTextFromAssets
|
||||
*/
|
||||
fun readTextFromAssets(app: AngApplication, fileName: String): String {
|
||||
val content = app.assets.open(fileName).bufferedReader().use {
|
||||
fun readTextFromAssets(context: Context, fileName: String): String {
|
||||
val content = context.assets.open(fileName).bufferedReader().use {
|
||||
it.readText()
|
||||
}
|
||||
return content
|
||||
@@ -438,9 +399,9 @@ object Utils {
|
||||
val command = "/system/bin/ping -c 3 $url"
|
||||
val process = Runtime.getRuntime().exec(command)
|
||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||
if (!TextUtils.isEmpty(allText)) {
|
||||
if (allText.isNotBlank()) {
|
||||
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) {
|
||||
return temps[0].toFloat().toInt().toString() + "ms"
|
||||
}
|
||||
@@ -454,19 +415,18 @@ object Utils {
|
||||
/**
|
||||
* tcping
|
||||
*/
|
||||
suspend fun tcping(url: String, port: Int): String {
|
||||
suspend fun tcping(url: String, port: Int): Long {
|
||||
var time = -1L
|
||||
for (k in 0 until 2) {
|
||||
val one = socketConnectTime(url, port)
|
||||
if (!coroutineContext.isActive) {
|
||||
break
|
||||
}
|
||||
if (one != -1L )
|
||||
if(time == -1L || one < time) {
|
||||
if (one != -1L && (time == -1L || one < time)) {
|
||||
time = one
|
||||
}
|
||||
}
|
||||
return time.toString() + "ms"
|
||||
return time
|
||||
}
|
||||
|
||||
fun socketConnectTime(url: String, port: Int): Long {
|
||||
@@ -501,5 +461,29 @@ object Utils {
|
||||
tcpTestingSockets.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getUrlContentWithCustomUserAgent(urlStr: String?): String {
|
||||
val url = URL(urlStr)
|
||||
val conn = url.openConnection()
|
||||
conn.setRequestProperty("Connection", "close")
|
||||
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
|
||||
url.userInfo?.let {
|
||||
conn.setRequestProperty("Authorization",
|
||||
"Basic ${encode(urlDecode(it))}")
|
||||
}
|
||||
conn.useCaches = false
|
||||
return conn.inputStream.use {
|
||||
it.bufferedReader().readText()
|
||||
}
|
||||
}
|
||||
|
||||
fun getIpv6Address(address: String): String {
|
||||
return if (isIpv6Address(address)) {
|
||||
String.format("[%s]", address)
|
||||
} else {
|
||||
address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,165 +3,122 @@ package com.v2ray.ang.util
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.google.gson.*
|
||||
import com.tencent.mmkv.MMKV
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashSet
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
||||
|
||||
object V2rayConfigUtil {
|
||||
private val requestObj: JsonObject by lazy {
|
||||
Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""", JsonObject::class.java)
|
||||
}
|
||||
|
||||
// private val responseObj: JSONObject by lazy {
|
||||
// JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""")
|
||||
// }
|
||||
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||
|
||||
data class Result(var status: Boolean, var content: String)
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
fun getV2rayConfig(app: AngApplication, vmess: VmessBean): Result {
|
||||
var result = Result(false, "")
|
||||
fun getV2rayConfig(context: Context, guid: String): Result {
|
||||
try {
|
||||
//检查设置
|
||||
// if (config.index < 0
|
||||
// || config.vmess.count() <= 0
|
||||
// || config.index > config.vmess.count() - 1
|
||||
// ) {
|
||||
// return result
|
||||
// }
|
||||
|
||||
if (vmess.configType == EConfigType.CUSTOM.value) {
|
||||
result = getV2rayConfigType2(app, vmess)
|
||||
} else {
|
||||
result = getV2rayConfigType1(app, vmess)
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
|
||||
if (config.configType == EConfigType.CUSTOM) {
|
||||
val raw = serverRawStorage?.decodeString(guid)
|
||||
val customConfig = if (raw.isNullOrBlank()) {
|
||||
config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
|
||||
} else {
|
||||
raw
|
||||
}
|
||||
Log.d(ANG_PACKAGE, customConfig)
|
||||
return Result(true, customConfig)
|
||||
}
|
||||
|
||||
Log.d("V2rayConfigUtilGoLog", result.content)
|
||||
val outbound = config.getProxyOutbound() ?: return Result(false, "")
|
||||
val result = getV2rayNonCustomConfig(context, outbound)
|
||||
Log.d(ANG_PACKAGE, result.content)
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
fun getCustomConfigServerOutbound(content: Context, guid: String): V2rayConfig.OutboundBean? {
|
||||
val jsonConfig = content.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
|
||||
if (TextUtils.isEmpty(jsonConfig)) {
|
||||
return null
|
||||
}
|
||||
val v2rayConfig = Gson().fromJson(jsonConfig, V2rayConfig::class.java) ?: return null
|
||||
for (outbound in v2rayConfig.outbounds) {
|
||||
if (outbound.protocol.equals(EConfigType.VMESS.name.toLowerCase()) ||
|
||||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) ||
|
||||
outbound.protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
return outbound
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result {
|
||||
val result = Result(false, "")
|
||||
try {
|
||||
//取得默认配置
|
||||
val assets = Utils.readTextFromAssets(app, "v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
|
||||
//转成Json
|
||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
// if (v2rayConfig == null) {
|
||||
// return result
|
||||
// }
|
||||
|
||||
inbounds(vmess, v2rayConfig, app)
|
||||
|
||||
outbounds(vmess, v2rayConfig, app)
|
||||
|
||||
routing(vmess, v2rayConfig, app)
|
||||
|
||||
if (app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)) {
|
||||
customLocalDns(vmess, v2rayConfig, app)
|
||||
} else {
|
||||
customRemoteDns(vmess, v2rayConfig, app)
|
||||
}
|
||||
|
||||
val finalConfig = GsonBuilder().setPrettyPrinting().create().toJson(v2rayConfig)
|
||||
|
||||
result.status = true
|
||||
result.content = finalConfig
|
||||
return result
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return result
|
||||
return Result(false, "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
private fun getV2rayConfigType2(app: AngApplication, vmess: VmessBean): Result {
|
||||
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): 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
|
||||
parseDomainNameAndTag(app, jsonConfig)
|
||||
return result
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
//取得默认配置
|
||||
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
|
||||
if (TextUtils.isEmpty(assets)) {
|
||||
return result
|
||||
}
|
||||
|
||||
//转成Json
|
||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||
|
||||
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
||||
|
||||
inbounds(v2rayConfig)
|
||||
|
||||
httpRequestObject(outbound)
|
||||
|
||||
v2rayConfig.outbounds[0] = outbound
|
||||
|
||||
routing(v2rayConfig)
|
||||
|
||||
fakedns(v2rayConfig)
|
||||
|
||||
dns(v2rayConfig)
|
||||
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||
customLocalDns(v2rayConfig)
|
||||
}
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
|
||||
v2rayConfig.stats = null
|
||||
v2rayConfig.policy = null
|
||||
}
|
||||
result.status = true
|
||||
result.content = v2rayConfig.toPrettyPrinting()
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
|
||||
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
|
||||
|
||||
v2rayConfig.inbounds.forEach { curInbound ->
|
||||
if (!app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
|
||||
//bind all inbounds to localhost if the user requests
|
||||
curInbound.listen = "127.0.0.1"
|
||||
}
|
||||
}
|
||||
v2rayConfig.inbounds[0].port = 10808
|
||||
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
||||
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, ""))
|
||||
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
|
||||
?: false
|
||||
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||
?: 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[0].port = socksPort
|
||||
// }
|
||||
// if (lanconnPort > 0) {
|
||||
//v2rayConfig.inbounds[1].port = httpPort
|
||||
|
||||
// if (httpPort > 0) {
|
||||
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
||||
// httpCopy.port = lanconnPort
|
||||
// httpCopy.port = httpPort
|
||||
// httpCopy.protocol = "http"
|
||||
// v2rayConfig.inbounds.add(httpCopy)
|
||||
// }
|
||||
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SNIFFING_ENABLED, true)
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
@@ -169,230 +126,39 @@ object V2rayConfigUtil {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* vmess协议服务器配置
|
||||
*/
|
||||
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
try {
|
||||
val outbound = v2rayConfig.outbounds[0]
|
||||
|
||||
val configType = EConfigType.fromInt(vmess.configType)
|
||||
if (configType != null) {
|
||||
outbound.protocol = configType.name.toLowerCase()
|
||||
private fun fakedns(v2rayConfig: V2rayConfig) {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
||||
v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
|
||||
it.settings?.domainStrategy = "UseIP"
|
||||
}
|
||||
when (configType) {
|
||||
EConfigType.VMESS -> {
|
||||
outbound.settings?.servers = null
|
||||
|
||||
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
|
||||
vnext?.address = vmess.address
|
||||
vnext?.port = vmess.port
|
||||
val user = vnext?.users?.get(0)
|
||||
user?.id = vmess.id
|
||||
user?.alterId = vmess.alterId
|
||||
user?.security = vmess.security
|
||||
user?.level = 8
|
||||
|
||||
//Mux
|
||||
val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false)
|
||||
outbound.mux?.enabled = muxEnabled
|
||||
|
||||
//远程服务器底层传输配置
|
||||
outbound.streamSettings = boundStreamSettings(vmess)
|
||||
}
|
||||
EConfigType.SHADOWSOCKS -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
server?.address = vmess.address
|
||||
server?.method = vmess.security
|
||||
server?.ota = false
|
||||
server?.password = vmess.id
|
||||
server?.port = vmess.port
|
||||
server?.level = 8
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
}
|
||||
EConfigType.SOCKS -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
server?.address = vmess.address
|
||||
server?.port = vmess.port
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
|
||||
String.format("[%s]:%s", vmess.address, vmess.port)
|
||||
} else {
|
||||
String.format("%s:%s", vmess.address, vmess.port)
|
||||
}
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, serverDomain)
|
||||
val tags = LinkedHashSet<String>()
|
||||
v2rayConfig.outbounds.forEach {
|
||||
if (!TextUtils.isEmpty(it.tag)) {
|
||||
tags.add(it.tag)
|
||||
}
|
||||
}
|
||||
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 远程服务器底层传输配置
|
||||
*/
|
||||
private fun boundStreamSettings(vmess: VmessBean): V2rayConfig.OutboundBean.StreamSettingsBean {
|
||||
val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null, null)
|
||||
try {
|
||||
//远程服务器底层传输配置
|
||||
streamSettings.network = vmess.network
|
||||
streamSettings.security = vmess.streamSecurity
|
||||
|
||||
//streamSettings
|
||||
when (streamSettings.network) {
|
||||
"kcp" -> {
|
||||
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
|
||||
kcpsettings.mtu = 1350
|
||||
kcpsettings.tti = 50
|
||||
kcpsettings.uplinkCapacity = 12
|
||||
kcpsettings.downlinkCapacity = 100
|
||||
kcpsettings.congestion = false
|
||||
kcpsettings.readBufferSize = 1
|
||||
kcpsettings.writeBufferSize = 1
|
||||
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
|
||||
kcpsettings.header.type = vmess.headerType
|
||||
streamSettings.kcpSettings = kcpsettings
|
||||
}
|
||||
"ws" -> {
|
||||
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean.HeadersBean()
|
||||
wssettings.headers.Host = host
|
||||
}
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
wssettings.path = path
|
||||
}
|
||||
streamSettings.wsSettings = wssettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
tlssettings.serverName = host
|
||||
}
|
||||
streamSettings.tlsSettings = tlssettings
|
||||
}
|
||||
"h2" -> {
|
||||
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
httpsettings.host = host.split(",").map { it.trim() }
|
||||
}
|
||||
httpsettings.path = path
|
||||
streamSettings.httpSettings = httpsettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
streamSettings.tlsSettings = tlssettings
|
||||
}
|
||||
"quic" -> {
|
||||
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
quicsettings.security = host
|
||||
quicsettings.key = path
|
||||
|
||||
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean.HeaderBean()
|
||||
quicsettings.header.type = vmess.headerType
|
||||
|
||||
streamSettings.quicSettings = quicsettings
|
||||
}
|
||||
else -> {
|
||||
//tcp带http伪装
|
||||
if (vmess.headerType == "http") {
|
||||
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
|
||||
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
|
||||
tcpSettings.header.type = vmess.headerType
|
||||
|
||||
// if (requestObj.has("headers")
|
||||
// || requestObj.optJSONObject("headers").has("Pragma")) {
|
||||
// val arrHost = ArrayList<String>()
|
||||
// vmess.requestHost
|
||||
// .split(",")
|
||||
// .forEach {
|
||||
// arrHost.add(it)
|
||||
// }
|
||||
// requestObj.optJSONObject("headers")
|
||||
// .put("Host", arrHost)
|
||||
//
|
||||
// }
|
||||
if (!TextUtils.isEmpty(vmess.requestHost)) {
|
||||
val arrHost = ArrayList<String>()
|
||||
vmess.requestHost
|
||||
.split(",")
|
||||
.forEach {
|
||||
arrHost.add("\"$it\"")
|
||||
}
|
||||
requestObj.getAsJsonObject("headers")
|
||||
.add("Host", Gson().fromJson(arrHost.toString(), JsonArray::class.java))
|
||||
}
|
||||
if (!TextUtils.isEmpty(vmess.path)) {
|
||||
val arrPath = ArrayList<String>()
|
||||
vmess.path
|
||||
.split(",")
|
||||
.forEach {
|
||||
arrPath.add("\"$it\"")
|
||||
}
|
||||
requestObj.add("path", Gson().fromJson(arrPath.toString(), JsonArray::class.java))
|
||||
}
|
||||
tcpSettings.header.request = requestObj
|
||||
//tcpSettings.header.response = responseObj
|
||||
streamSettings.tcpSettings = tcpSettings
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return streamSettings
|
||||
}
|
||||
return streamSettings
|
||||
}
|
||||
|
||||
/**
|
||||
* routing
|
||||
*/
|
||||
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun routing(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig)
|
||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig)
|
||||
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
?: "", AppConfig.TAG_AGENT, v2rayConfig)
|
||||
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
?: "", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||
?: "", AppConfig.TAG_BLOCKED, v2rayConfig)
|
||||
|
||||
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
|
||||
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||
?: "IPIfNonMatch"
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||
|
||||
// Hardcode googleapis.cn
|
||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
domain = arrayListOf("domain:googleapis.cn")
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
domain = arrayListOf("domain:googleapis.cn")
|
||||
)
|
||||
|
||||
when (routingMode) {
|
||||
"0" -> {
|
||||
}
|
||||
"1" -> {
|
||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||
}
|
||||
@@ -421,7 +187,7 @@ object V2rayConfigUtil {
|
||||
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesIP.type = "field"
|
||||
rulesIP.outboundTag = tag
|
||||
rulesIP.ip = ArrayList<String>()
|
||||
rulesIP.ip = ArrayList()
|
||||
rulesIP.ip?.add("geoip:$code")
|
||||
v2rayConfig.routing.rules.add(rulesIP)
|
||||
}
|
||||
@@ -431,7 +197,7 @@ object V2rayConfigUtil {
|
||||
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesDomain.type = "field"
|
||||
rulesDomain.outboundTag = tag
|
||||
rulesDomain.domain = ArrayList<String>()
|
||||
rulesDomain.domain = ArrayList()
|
||||
rulesDomain.domain?.add("geosite:$code")
|
||||
v2rayConfig.routing.rules.add(rulesDomain)
|
||||
}
|
||||
@@ -448,29 +214,27 @@ object V2rayConfigUtil {
|
||||
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesDomain.type = "field"
|
||||
rulesDomain.outboundTag = tag
|
||||
rulesDomain.domain = ArrayList<String>()
|
||||
rulesDomain.domain = ArrayList()
|
||||
|
||||
//IP
|
||||
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
||||
rulesIP.type = "field"
|
||||
rulesIP.outboundTag = tag
|
||||
rulesIP.ip = ArrayList<String>()
|
||||
rulesIP.ip = ArrayList()
|
||||
|
||||
userRule.trim().replace("\n", "")
|
||||
.split(",")
|
||||
.forEach {
|
||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||
rulesIP.ip?.add(it)
|
||||
} else if (it.isNotBlank() || it.isNotEmpty())
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||
rulesIP.ip?.add(it)
|
||||
} else if (it.isNotEmpty())
|
||||
// if (Utils.isValidUrl(it)
|
||||
// || it.startsWith("geosite:")
|
||||
// || it.startsWith("regexp:")
|
||||
// || it.startsWith("domain:")
|
||||
// || it.startsWith("full:"))
|
||||
{
|
||||
rulesDomain.domain?.add(it)
|
||||
}
|
||||
}
|
||||
{
|
||||
rulesDomain.domain?.add(it)
|
||||
}
|
||||
}
|
||||
if (rulesDomain.domain?.size!! > 0) {
|
||||
v2rayConfig.routing.rules.add(rulesDomain)
|
||||
}
|
||||
@@ -485,9 +249,8 @@ object V2rayConfigUtil {
|
||||
|
||||
private fun userRule2Domian(userRule: String): ArrayList<String> {
|
||||
val domain = ArrayList<String>()
|
||||
userRule.trim().replace("\n", "").split(",").forEach {
|
||||
if ((it.startsWith("geosite:") || it.startsWith("domain:")) &&
|
||||
it.isNotBlank() && it.isNotEmpty()) {
|
||||
userRule.split(",").map { it.trim() }.forEach {
|
||||
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
|
||||
domain.add(it)
|
||||
}
|
||||
}
|
||||
@@ -497,98 +260,127 @@ object V2rayConfigUtil {
|
||||
/**
|
||||
* Custom Dns
|
||||
*/
|
||||
private fun customLocalDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
?: "")
|
||||
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
?: "")
|
||||
// fakedns with all domains to make it always top priority
|
||||
v2rayConfig.dns.servers?.add(0,
|
||||
V2rayConfig.DnsBean.ServersBean(address = "fakedns", domains = geositeCn.plus(proxyDomain).plus(directDomain)))
|
||||
}
|
||||
|
||||
// DNS inbound对象
|
||||
val remoteDns = Utils.getRemoteDnsServers()
|
||||
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
||||
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
||||
address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else "1.1.1.1",
|
||||
port = 53,
|
||||
network = "tcp,udp")
|
||||
|
||||
//val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT) ?: AppConfig.PORT_LOCAL_DNS)
|
||||
v2rayConfig.inbounds.add(
|
||||
V2rayConfig.InboundBean(
|
||||
tag = "dns-in",
|
||||
port = 10807,
|
||||
listen = "127.0.0.1",
|
||||
protocol = "dokodemo-door",
|
||||
settings = dnsInboundSettings,
|
||||
sniffing = null))
|
||||
}
|
||||
|
||||
// DNS outbound对象
|
||||
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
|
||||
v2rayConfig.outbounds.add(
|
||||
V2rayConfig.OutboundBean(
|
||||
protocol = "dns",
|
||||
tag = "dns-out",
|
||||
settings = null,
|
||||
streamSettings = null,
|
||||
mux = null))
|
||||
}
|
||||
|
||||
// DNS routing tag
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
inboundTag = arrayListOf("dns-in"),
|
||||
outboundTag = "dns-out",
|
||||
domain = null)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun dns(v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
val hosts = mutableMapOf<String, String>()
|
||||
val servers = ArrayList<Any>()
|
||||
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference)
|
||||
val remoteDns = Utils.getRemoteDnsServers()
|
||||
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
|
||||
?: "")
|
||||
|
||||
remoteDns.forEach {
|
||||
servers.add(it)
|
||||
}
|
||||
|
||||
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference)
|
||||
|
||||
val agDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
|
||||
if (agDomain.size > 0) {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain))
|
||||
if (proxyDomain.size > 0) {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, proxyDomain, null))
|
||||
}
|
||||
|
||||
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
|
||||
if (dirDomain.size > 0) {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain))
|
||||
// domestic DNS
|
||||
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||
?: "")
|
||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
||||
if (directDomain.size > 0 || routingMode == "2" || routingMode == "3") {
|
||||
val domesticDns = Utils.getDomesticDnsServers()
|
||||
val geositeCn = arrayListOf("geosite:cn")
|
||||
val geoipCn = arrayListOf("geoip:cn")
|
||||
if (directDomain.size > 0) {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
|
||||
}
|
||||
if (routingMode == "2" || routingMode == "3") {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
|
||||
}
|
||||
if (Utils.isPureIpAddress(domesticDns.first())) {
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
port = "53",
|
||||
ip = arrayListOf(domesticDns.first()),
|
||||
domain = null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
if (routingMode == "2" || routingMode == "3") {
|
||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, arrayListOf("geosite:cn")))
|
||||
}
|
||||
|
||||
val blkDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
|
||||
val blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
|
||||
?: "")
|
||||
if (blkDomain.size > 0) {
|
||||
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
|
||||
}
|
||||
|
||||
// hardcode googleapi rule to fix play store problems
|
||||
hosts.put("domain:googleapis.cn", "googleapis.com")
|
||||
hosts["domain:googleapis.cn"] = "googleapis.com"
|
||||
|
||||
// DNS dns对象
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(
|
||||
servers = servers,
|
||||
hosts = hosts)
|
||||
|
||||
// DNS inbound对象
|
||||
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
||||
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
||||
address = remoteDns.first(),
|
||||
port = 53,
|
||||
network = "tcp,udp")
|
||||
|
||||
v2rayConfig.inbounds.add(
|
||||
V2rayConfig.InboundBean(
|
||||
tag = "dns-in",
|
||||
port = 10807,
|
||||
listen = "127.0.0.1",
|
||||
protocol = "dokodemo-door",
|
||||
settings = dnsInboundSettings,
|
||||
sniffing = null))
|
||||
}
|
||||
|
||||
// DNS outbound对象
|
||||
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
|
||||
v2rayConfig.outbounds.add(
|
||||
V2rayConfig.OutboundBean(
|
||||
protocol = "dns",
|
||||
tag = "dns-out",
|
||||
settings = null,
|
||||
streamSettings = null,
|
||||
mux = null))
|
||||
}
|
||||
|
||||
// DNS routing
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
port = "53",
|
||||
ip = domesticDns,
|
||||
domain = null)
|
||||
)
|
||||
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
port = "53",
|
||||
ip = remoteDns,
|
||||
domain = null)
|
||||
)
|
||||
|
||||
// DNS routing tag
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
inboundTag = arrayListOf<String>("dns-in"),
|
||||
outboundTag = "dns-out",
|
||||
domain = null)
|
||||
)
|
||||
|
||||
if (Utils.isPureIpAddress(remoteDns.first())) {
|
||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||
type = "field",
|
||||
outboundTag = AppConfig.TAG_AGENT,
|
||||
port = "53",
|
||||
ip = arrayListOf(remoteDns.first()),
|
||||
domain = null)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
@@ -596,115 +388,34 @@ object V2rayConfigUtil {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom Remote Dns
|
||||
*/
|
||||
private fun customRemoteDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
|
||||
private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean {
|
||||
try {
|
||||
val servers = ArrayList<Any>()
|
||||
if (outbound.streamSettings?.network == DEFAULT_NETWORK
|
||||
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
|
||||
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
|
||||
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
||||
|
||||
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
|
||||
servers.add(it)
|
||||
}
|
||||
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* is valid config
|
||||
*/
|
||||
fun isValidConfig(conf: String): Boolean {
|
||||
try {
|
||||
val jObj = JSONObject(conf)
|
||||
var hasBound = false
|
||||
//hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound"))
|
||||
hasBound = (jObj.has("outbounds")) or (jObj.has("outbound"))
|
||||
return hasBound
|
||||
} catch (e: JSONException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
|
||||
try {
|
||||
val jObj = JSONObject(jsonConfig)
|
||||
var domainName = ""
|
||||
val tags = LinkedHashSet<String>()
|
||||
if (jObj.has("outbound")) {
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
|
||||
domainName = domain
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
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"}}"""
|
||||
}
|
||||
}
|
||||
if (jObj.has("outbounds")) {
|
||||
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outbounds").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
|
||||
domainName = domain
|
||||
}
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jObj.has("outboundDetour")) {
|
||||
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outboundDetour").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
|
||||
domainName = domain
|
||||
}
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
|
||||
}
|
||||
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDomainNameAndTag(outbound: JSONObject): Pair<String, String> {
|
||||
val tag = if (outbound.has("tag")) {
|
||||
outbound.getString("tag")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
try {
|
||||
if (outbound.has("settings")) {
|
||||
val vnext: JSONArray?
|
||||
if (outbound.optJSONObject("settings").has("vnext")) {
|
||||
// vmess
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
|
||||
} else if (outbound.optJSONObject("settings").has("servers")) {
|
||||
// shadowsocks or socks
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
|
||||
} else {
|
||||
return Pair("", tag)
|
||||
}
|
||||
for (i in 0..(vnext.length() - 1)) {
|
||||
val item = vnext.getJSONObject(i)
|
||||
val address = item.getString("address")
|
||||
val port = item.getString("port")
|
||||
if(Utils.isIpv6Address(address)) {
|
||||
return Pair(String.format("[%s]:%s", address, port), tag)
|
||||
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(
|
||||
requestString,
|
||||
V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java
|
||||
)
|
||||
outbound.streamSettings?.tcpSettings?.header?.request?.path =
|
||||
if (path.isNullOrEmpty()) {
|
||||
listOf("/")
|
||||
} else {
|
||||
return Pair(String.format("%s:%s", address, port), tag)
|
||||
path
|
||||
}
|
||||
}
|
||||
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!!
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
}
|
||||
return Pair("", tag)
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.v2ray.ang.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.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 androidx.lifecycle.AndroidViewModel
|
||||
import android.content.SharedPreferences
|
||||
import androidx.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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,17 @@
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:context=".ui.PerAppProxyActivity" />
|
||||
|
||||
<android.support.v7.widget.LinearLayoutCompat
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:id="@+id/header_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -37,7 +37,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bypass_list_header_height">
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switch_per_app_proxy"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -45,7 +45,7 @@
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<android.support.v7.widget.AppCompatTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
@@ -63,7 +63,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bypass_list_header_height">
|
||||
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/switch_bypass_apps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -71,7 +71,7 @@
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true" />
|
||||
|
||||
<android.support.v7.widget.AppCompatTextView
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/tv_bypass_apps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -89,11 +89,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bypass_list_header_height">
|
||||
|
||||
<android.support.design.widget.TextInputLayout
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.AppCompatEditText
|
||||
<androidx.appcompat.widget.AppCompatEditText
|
||||
android:id="@+id/et_search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -103,11 +103,11 @@
|
||||
android:maxLines="1"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
</android.support.design.widget.TextInputLayout>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</android.support.v7.widget.LinearLayoutCompat>
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user