Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c2bc86457 | ||
|
|
68a03a93b5 | ||
|
|
5127a30ae9 | ||
|
|
8b76a7a4f4 | ||
|
|
b6aec3fd63 | ||
|
|
18e0dc4546 | ||
|
|
129c1db995 | ||
|
|
56d987713e | ||
|
|
35e57977ea | ||
|
|
34c14d1f1a | ||
|
|
bce08c5136 | ||
|
|
2d90a07788 | ||
|
|
b4204ed3a8 | ||
|
|
d30fb697bb | ||
|
|
18ff11dbba | ||
|
|
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 |
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
|||||||
V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml
|
|
||||||
V2rayNG/app/src/main/assets/geoip.dat
|
|
||||||
V2rayNG/app/src/main/assets/geosite.dat
|
|
||||||
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
|
|
||||||
*.dat
|
*.dat
|
||||||
*.jks
|
*.jks
|
||||||
V2rayNG/app/release/output.json
|
V2rayNG/app/release/output.json
|
||||||
|
|||||||
2
AndroidLibV2rayLite/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
*.jar
|
|
||||||
*.aar
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package CoreI
|
|
||||||
|
|
||||||
import (
|
|
||||||
v2core "github.com/xtls/xray-core/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Status struct {
|
|
||||||
IsRunning bool
|
|
||||||
IsTRunning bool
|
|
||||||
PackageName string
|
|
||||||
PackageCodePath string
|
|
||||||
|
|
||||||
Vpoint v2core.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckVersion() int {
|
|
||||||
return 23
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Status) GetDataDir() string {
|
|
||||||
return v.PackageName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Status) GetApp(name string) string {
|
|
||||||
return v.PackageCodePath + name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
|
|
||||||
ret = []string{"--netif-ipaddr",
|
|
||||||
"26.26.26.2",
|
|
||||||
"--netif-netmask",
|
|
||||||
"255.255.255.252",
|
|
||||||
"--socks-server-addr",
|
|
||||||
"127.0.0.1:10808",
|
|
||||||
"--tunmtu",
|
|
||||||
"1500",
|
|
||||||
"--loglevel",
|
|
||||||
"notice",
|
|
||||||
"--enable-udprelay",
|
|
||||||
"--sock-path",
|
|
||||||
v.GetDataDir() + "sock_path",
|
|
||||||
}
|
|
||||||
|
|
||||||
if enableIPv6 {
|
|
||||||
ret = append(ret, "--netif-ip6addr", "da26:2626::2")
|
|
||||||
}
|
|
||||||
|
|
||||||
if localDNS {
|
|
||||||
ret = append(ret, "--dnsgw", "127.0.0.1:10807")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Status) GetVPNSetupArg(localDNS bool, enableIPv6 bool) (ret string) {
|
|
||||||
ret = "m,1500 a,26.26.26.1,30 r,0.0.0.0,0"
|
|
||||||
|
|
||||||
if enableIPv6 {
|
|
||||||
ret += " a,da26:2626::1,126 r,::,0"
|
|
||||||
}
|
|
||||||
if localDNS {
|
|
||||||
ret += " d,26.26.26.2"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
pb:
|
|
||||||
go get -u github.com/golang/protobuf/protoc-gen-go
|
|
||||||
@echo "pb Start"
|
|
||||||
asset:
|
|
||||||
bash gen_assets.sh download
|
|
||||||
mkdir assets
|
|
||||||
cp -v data/*.dat assets/
|
|
||||||
# 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
|
|
||||||
|
|
||||||
ANDROID_HOME=$(HOME)/android-sdk-linux
|
|
||||||
export ANDROID_HOME
|
|
||||||
PATH:=$(PATH):$(GOPATH)/bin
|
|
||||||
export PATH
|
|
||||||
downloadGoMobile:
|
|
||||||
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/
|
|
||||||
|
|
||||||
BuildMobile:
|
|
||||||
gomobile init
|
|
||||||
gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
|
|
||||||
|
|
||||||
all: asset pb
|
|
||||||
@echo DONE
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package Escort
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string) {
|
|
||||||
log.Println(proc, pt)
|
|
||||||
count := 0
|
|
||||||
for count <= 42 {
|
|
||||||
cmd := exec.Command(proc, pt...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
|
|
||||||
if len(additionalEnv) > 0 {
|
|
||||||
//additionalEnv := "FOO=bar"
|
|
||||||
newEnv := append(os.Environ(), additionalEnv)
|
|
||||||
cmd.Env = newEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
log.Println("EscortRun cmd.Start err", err)
|
|
||||||
goto CMDERROR
|
|
||||||
}
|
|
||||||
|
|
||||||
if v.escortProcess == nil {
|
|
||||||
log.Println("EscortRun v.escortProcess nil")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
*v.escortProcess = append(*v.escortProcess, cmd.Process)
|
|
||||||
log.Println("EscortRun Waiting....")
|
|
||||||
|
|
||||||
if err := cmd.Wait(); err != nil {
|
|
||||||
log.Println("EscortRun cmd.Wait err:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
CMDERROR:
|
|
||||||
if v.Status.IsRunning {
|
|
||||||
log.Println("EscortRun Unexpected Exit, Restart now.")
|
|
||||||
count++
|
|
||||||
} else {
|
|
||||||
log.Println("EscortRun Exit")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Escorting) EscortingUp() {
|
|
||||||
if v.escortProcess != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
v.escortProcess = new([](*os.Process))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Escorting) EscortingDown() {
|
|
||||||
if v.escortProcess == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("EscortingDown() Killing all escorted process ")
|
|
||||||
for _, pr := range *v.escortProcess {
|
|
||||||
pr.Kill()
|
|
||||||
if _, err := pr.Wait(); err != nil {
|
|
||||||
log.Println("EscortingDown pr.Wait err:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.escortProcess = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Escorting struct {
|
|
||||||
escortProcess *[](*os.Process)
|
|
||||||
Status *CoreI.Status
|
|
||||||
}
|
|
||||||
@@ -1 +1,20 @@
|
|||||||
# AndroidLibV2rayLite
|
# AndroidLibV2rayLite
|
||||||
|
|
||||||
|
### Preparation
|
||||||
|
- latest Ubuntu environment
|
||||||
|
- At lease 30G free space
|
||||||
|
- Get Repo [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
|
||||||
|
### Prepare Go
|
||||||
|
- Go to https://golang.org/doc/install and install latest go
|
||||||
|
- Make sure `go version` works as expected
|
||||||
|
### Prepare gomobile
|
||||||
|
- Go to https://pkg.go.dev/golang.org/x/mobile/cmd/gomobile and install gomobile
|
||||||
|
- export PATH=$PATH:~/go/bin
|
||||||
|
- Make sure `gomobile init` works as expected
|
||||||
|
### Prepare NDK
|
||||||
|
- Go to https://developer.android.com/ndk/downloads and install latest NDK
|
||||||
|
- export PATH=$PATH:<wherever you ndk is located>
|
||||||
|
- Make sure `ndk-build -v` works as expected
|
||||||
|
### Make
|
||||||
|
- sudo apt install make
|
||||||
|
- Read and understand [build script](https://github.com/2dust/AndroidLibV2rayLite/blob/master/Makefile)
|
||||||
|
|||||||
@@ -1,286 +0,0 @@
|
|||||||
package VPN
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
v2net "github.com/xtls/xray-core/common/net"
|
|
||||||
"github.com/xtls/xray-core/features/dns"
|
|
||||||
"github.com/xtls/xray-core/features/outbound"
|
|
||||||
v2internet "github.com/xtls/xray-core/transport/internet"
|
|
||||||
)
|
|
||||||
|
|
||||||
type protectSet interface {
|
|
||||||
Protect(int) int
|
|
||||||
}
|
|
||||||
|
|
||||||
type resolved struct {
|
|
||||||
domain string
|
|
||||||
IPs []net.IP
|
|
||||||
Port int
|
|
||||||
ipIdx uint8
|
|
||||||
ipLock sync.Mutex
|
|
||||||
lastSwitched time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// NextIP switch to another resolved result.
|
|
||||||
// there still be race-condition here if multiple err concurently occured
|
|
||||||
// may cause idx keep switching,
|
|
||||||
// but that's an outside error can hardly handled here
|
|
||||||
func (r *resolved) NextIP() {
|
|
||||||
r.ipLock.Lock()
|
|
||||||
defer r.ipLock.Unlock()
|
|
||||||
|
|
||||||
if len(r.IPs) > 1 {
|
|
||||||
|
|
||||||
// throttle, don't switch too quickly
|
|
||||||
now := time.Now()
|
|
||||||
if now.Sub(r.lastSwitched) < time.Second*5 {
|
|
||||||
log.Println("switch too quickly")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.lastSwitched = now
|
|
||||||
r.ipIdx++
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.ipIdx >= uint8(len(r.IPs)) {
|
|
||||||
r.ipIdx = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
cur := r.currentIP()
|
|
||||||
log.Printf("switched to next IP: %s", cur)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *resolved) currentIP() net.IP {
|
|
||||||
if len(r.IPs) > 0 {
|
|
||||||
return r.IPs[r.ipIdx]
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPreotectedDialer ...
|
|
||||||
func NewPreotectedDialer(p protectSet) *ProtectedDialer {
|
|
||||||
d := &ProtectedDialer{
|
|
||||||
// prefer native lookup on Android
|
|
||||||
resolver: &net.Resolver{PreferGo: false},
|
|
||||||
protectSet: p,
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProtectedDialer ...
|
|
||||||
type ProtectedDialer struct {
|
|
||||||
currentServer string
|
|
||||||
resolveChan chan struct{}
|
|
||||||
|
|
||||||
vServer *resolved
|
|
||||||
resolver *net.Resolver
|
|
||||||
|
|
||||||
protectSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ProtectedDialer) IsVServerReady() bool {
|
|
||||||
return (d.vServer != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ProtectedDialer) PrepareResolveChan() {
|
|
||||||
d.resolveChan = make(chan struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ProtectedDialer) ResolveChan() <-chan struct{} {
|
|
||||||
return d.resolveChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// simplicated version of golang: internetAddrList in src/net/ipsock.go
|
|
||||||
func (d *ProtectedDialer) lookupAddr(addr string) (*resolved, error) {
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
host, port string
|
|
||||||
portnum int
|
|
||||||
)
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if host, port, err = net.SplitHostPort(addr); err != nil {
|
|
||||||
log.Printf("PrepareDomain SplitHostPort Err: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if portnum, err = d.resolver.LookupPort(ctx, "tcp", port); err != nil {
|
|
||||||
log.Printf("PrepareDomain LookupPort Err: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addrs, err := d.resolver.LookupIPAddr(ctx, host)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(addrs) == 0 {
|
|
||||||
return nil, fmt.Errorf("domain %s Failed to resolve", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
IPs := make([]net.IP, len(addrs))
|
|
||||||
for i, ia := range addrs {
|
|
||||||
IPs[i] = ia.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
rs := &resolved{
|
|
||||||
domain: host,
|
|
||||||
IPs: IPs,
|
|
||||||
Port: portnum,
|
|
||||||
}
|
|
||||||
|
|
||||||
return rs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareDomain caches direct v2ray server host
|
|
||||||
func (d *ProtectedDialer) PrepareDomain(domainName string, closeCh <-chan struct{}) {
|
|
||||||
log.Printf("Preparing Domain: %s", domainName)
|
|
||||||
d.currentServer = domainName
|
|
||||||
|
|
||||||
defer close(d.resolveChan)
|
|
||||||
maxRetry := 10
|
|
||||||
for {
|
|
||||||
if maxRetry == 0 {
|
|
||||||
log.Println("PrepareDomain maxRetry reached. exiting.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resolved, err := d.lookupAddr(domainName)
|
|
||||||
if err != nil {
|
|
||||||
maxRetry--
|
|
||||||
log.Printf("PrepareDomain err: %v\n", err)
|
|
||||||
select {
|
|
||||||
case <-closeCh:
|
|
||||||
log.Printf("PrepareDomain exit due to v2ray closed")
|
|
||||||
return
|
|
||||||
case <-time.After(time.Second * 2):
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d.vServer = resolved
|
|
||||||
log.Printf("Prepare Result:\n Domain: %s\n Port: %d\n IPs: %v\n",
|
|
||||||
resolved.domain, resolved.Port, resolved.IPs)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
|
|
||||||
switch network {
|
|
||||||
case v2net.Network_TCP:
|
|
||||||
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, unix.IPPROTO_TCP)
|
|
||||||
case v2net.Network_UDP:
|
|
||||||
fd, err = unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unknow network")
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
|
|
||||||
network := dest.Network.SystemString()
|
|
||||||
Address := dest.NetAddr()
|
|
||||||
|
|
||||||
// v2ray server address,
|
|
||||||
// try to connect fixed IP if multiple IP parsed from domain,
|
|
||||||
// and switch to next IP if error occurred
|
|
||||||
if strings.Compare(Address, d.currentServer) == 0 {
|
|
||||||
if d.vServer == nil {
|
|
||||||
log.Println("Dial pending prepare ...", Address)
|
|
||||||
<-d.resolveChan
|
|
||||||
|
|
||||||
// user may close connection during PrepareDomain,
|
|
||||||
// fast return release resources.
|
|
||||||
if d.vServer == nil {
|
|
||||||
return nil, fmt.Errorf("fail to prepare domain %s", d.currentServer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := d.getFd(dest.Network)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
curIP := d.vServer.currentIP()
|
|
||||||
conn, err := d.fdConn(ctx, curIP, d.vServer.Port, fd)
|
|
||||||
if err != nil {
|
|
||||||
d.vServer.NextIP()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Printf("Using Prepared: %s", curIP)
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// v2ray connecting to "domestic" servers, no caching results
|
|
||||||
log.Printf("Not Using Prepared: %s,%s", network, Address)
|
|
||||||
resolved, err := d.lookupAddr(Address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fd, err := d.getFd(dest.Network)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the first resolved address.
|
|
||||||
// the result IP may vary, eg: IPv6 addrs comes first if client has ipv6 address
|
|
||||||
return d.fdConn(ctx, resolved.IPs[0], resolved.Port, fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ProtectedDialer) fdConn(ctx context.Context, ip net.IP, port int, fd int) (net.Conn, error) {
|
|
||||||
|
|
||||||
defer unix.Close(fd)
|
|
||||||
|
|
||||||
// call android VPN service to "protect" the fd connecting straight out
|
|
||||||
d.Protect(fd)
|
|
||||||
|
|
||||||
sa := &unix.SockaddrInet6{
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
copy(sa.Addr[:], ip)
|
|
||||||
|
|
||||||
if err := unix.Connect(fd, sa); err != nil {
|
|
||||||
log.Printf("fdConn unix.Connect err, Close Fd: %d Err: %v", fd, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
file := os.NewFile(uintptr(fd), "Socket")
|
|
||||||
if file == nil {
|
|
||||||
// returned value will be nil if fd is not a valid file descriptor
|
|
||||||
return nil, errors.New("fdConn fd invalid")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
//Closing conn does not affect file, and closing file does not affect conn.
|
|
||||||
conn, err := net.FileConn(file)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("fdConn FileConn Close Fd: %d Err: %v", fd, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
package VPN
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
v2net "github.com/xtls/xray-core/common/net"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeSupportSet struct{}
|
|
||||||
|
|
||||||
func (f fakeSupportSet) Protect(int) int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProtectedDialer_PrepareDomain(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
domainName string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
}{
|
|
||||||
// TODO: Add test cases.
|
|
||||||
{"", args{"baidu.com:80"}},
|
|
||||||
// {"", args{"cloudflare.com:443"}},
|
|
||||||
// {"", args{"apple.com:443"}},
|
|
||||||
// {"", args{"110.110.110.110:443"}},
|
|
||||||
// {"", args{"[2002:1234::1]:443"}},
|
|
||||||
}
|
|
||||||
d := NewPreotectedDialer(fakeSupportSet{})
|
|
||||||
for _, tt := range tests {
|
|
||||||
ch := make(chan struct{})
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
go d.PrepareDomain(tt.args.domainName, ch)
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
go d.vServer.NextIP()
|
|
||||||
t.Log(d.vServer.currentIP())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProtectedDialer_Dial(t *testing.T) {
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
// TODO: Add test cases.
|
|
||||||
{"baidu.com:80", false},
|
|
||||||
{"cloudflare.com:80", false},
|
|
||||||
{"172.16.192.11:80", true},
|
|
||||||
// {"172.16.192.10:80", true},
|
|
||||||
// {"[2fff:4322::1]:443", true},
|
|
||||||
// {"[fc00::1]:443", true},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ch := make(chan struct{})
|
|
||||||
|
|
||||||
d := NewPreotectedDialer(fakeSupportSet{})
|
|
||||||
d.currentServer = tt.name
|
|
||||||
|
|
||||||
go d.PrepareDomain(tt.name, ch)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
dial := func() {
|
|
||||||
defer wg.Done()
|
|
||||||
dest, _ := v2net.ParseDestination("tcp:" + tt.name)
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
conn, err := d.Dial(ctx, nil, dest, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Log(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_host, _, _ := net.SplitHostPort(tt.name)
|
|
||||||
fmt.Fprintf(conn, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", _host))
|
|
||||||
status, err := bufio.NewReader(conn).ReadString('\n')
|
|
||||||
t.Logf("%#v, %#v\n", status, err)
|
|
||||||
conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := 0; n < 3; n++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go dial()
|
|
||||||
// time.Sleep(time.Millisecond * 10)
|
|
||||||
// d.pendingMap[tt.name] = make(chan struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_resolved_NextIP(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
domain string
|
|
||||||
IPs []net.IP
|
|
||||||
Port int
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
}{
|
|
||||||
// TODO: Add test cases.
|
|
||||||
{"test1",
|
|
||||||
fields{
|
|
||||||
domain: "www.baidu.com",
|
|
||||||
IPs: []net.IP{
|
|
||||||
net.ParseIP("1.2.3.4"),
|
|
||||||
net.ParseIP("4.3.2.1"),
|
|
||||||
net.ParseIP("1234::1"),
|
|
||||||
net.ParseIP("4321::1"),
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
r := &resolved{
|
|
||||||
domain: tt.fields.domain,
|
|
||||||
IPs: tt.fields.IPs,
|
|
||||||
Port: tt.fields.Port,
|
|
||||||
}
|
|
||||||
t.Logf("%v", r.IPs)
|
|
||||||
t.Logf("%v", r.currentIP())
|
|
||||||
r.NextIP()
|
|
||||||
t.Logf("%v", r.currentIP())
|
|
||||||
r.NextIP()
|
|
||||||
t.Logf("%v", r.currentIP())
|
|
||||||
r.NextIP()
|
|
||||||
t.Logf("%v", r.currentIP())
|
|
||||||
time.Sleep(3 * time.Second)
|
|
||||||
r.NextIP()
|
|
||||||
t.Logf("%v", r.currentIP())
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
r.NextIP()
|
|
||||||
t.Logf("%v", r.currentIP())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o pipefail
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
# Set magic variables for current file & dir
|
|
||||||
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
|
|
||||||
__base="$(basename ${__file} .sh)"
|
|
||||||
|
|
||||||
if [[ ! -d $NDK_HOME ]]; then
|
|
||||||
echo "Android NDK: NDK_HOME not found. please set env \$NDK_HOME"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
TMPDIR=$(mktemp -d)
|
|
||||||
clear_tmp () {
|
|
||||||
rm -rf $TMPDIR
|
|
||||||
}
|
|
||||||
|
|
||||||
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp; exit 1' ERR INT
|
|
||||||
install -m644 $__dir/tun2socks.mk $TMPDIR/
|
|
||||||
|
|
||||||
pushd $TMPDIR
|
|
||||||
git clone --depth=1 https://github.com/XTLS/badvpn.git
|
|
||||||
git clone --depth=1 https://github.com/XTLS/libancillary.git
|
|
||||||
$NDK_HOME/ndk-build \
|
|
||||||
NDK_PROJECT_PATH=. \
|
|
||||||
APP_BUILD_SCRIPT=./tun2socks.mk \
|
|
||||||
APP_ABI=all \
|
|
||||||
APP_PLATFORM=android-19 \
|
|
||||||
NDK_LIBS_OUT=$TMPDIR/libs \
|
|
||||||
NDK_OUT=$TMPDIR/tmp \
|
|
||||||
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
|
|
||||||
|
|
||||||
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
|
|
||||||
install -v -m755 libs/arm64-v8a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
|
|
||||||
install -v -m755 libs/x86/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
|
|
||||||
install -v -m755 libs/x86_64/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
|
|
||||||
popd
|
|
||||||
|
|
||||||
rm -rf $TMPDIR
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o pipefail
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
# Set magic variables for current file & dir
|
|
||||||
__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
__file="${__dir}/$(basename "${BASH_SOURCE[0]}")"
|
|
||||||
__base="$(basename ${__file} .sh)"
|
|
||||||
|
|
||||||
|
|
||||||
DATADIR=${__dir}/data
|
|
||||||
|
|
||||||
compile_dat () {
|
|
||||||
local TMPDIR=$(mktemp -d)
|
|
||||||
|
|
||||||
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; rm -rf $TMPDIR; trap ERR; exit 1' ERR
|
|
||||||
|
|
||||||
local GEOSITE=${GOPATH}/src/github.com/v2ray/domain-list-community
|
|
||||||
if [[ -d ${GEOSITE} ]]; then
|
|
||||||
cd ${GEOSITE} && git pull
|
|
||||||
else
|
|
||||||
mkdir -p ${GEOSITE}
|
|
||||||
cd ${GEOSITE} && git clone https://github.com/v2ray/domain-list-community.git .
|
|
||||||
fi
|
|
||||||
go run main.go
|
|
||||||
|
|
||||||
if [[ -e dlc.dat ]]; then
|
|
||||||
rm -f $DATADIR/geosite.dat
|
|
||||||
mv dlc.dat $DATADIR/geosite.dat
|
|
||||||
echo "----------> geosite.dat updated."
|
|
||||||
else
|
|
||||||
echo "----------> geosite.dat failed to update."
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
if [[ ! -x $GOPATH/bin/geoip ]]; then
|
|
||||||
go get -v -u github.com/v2ray/geoip
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd $TMPDIR
|
|
||||||
curl -L -O http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country-CSV.zip
|
|
||||||
unzip -q GeoLite2-Country-CSV.zip
|
|
||||||
mkdir geoip && find . -name '*.csv' -exec mv -t ./geoip {} +
|
|
||||||
$GOPATH/bin/geoip \
|
|
||||||
--country=./geoip/GeoLite2-Country-Locations-en.csv \
|
|
||||||
--ipv4=./geoip/GeoLite2-Country-Blocks-IPv4.csv \
|
|
||||||
--ipv6=./geoip/GeoLite2-Country-Blocks-IPv6.csv
|
|
||||||
|
|
||||||
if [[ -e geoip.dat ]]; then
|
|
||||||
rm -f $DATADIR/geoip.dat
|
|
||||||
mv ./geoip.dat $DATADIR/geoip.dat
|
|
||||||
echo "----------> geoip.dat updated."
|
|
||||||
else
|
|
||||||
echo "----------> geoip.dat failed to update."
|
|
||||||
fi
|
|
||||||
trap ERR
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
download_dat () {
|
|
||||||
wget -qO - https://api.github.com/repos/v2ray/geoip/releases/latest \
|
|
||||||
| grep browser_download_url | cut -d '"' -f 4 \
|
|
||||||
| wget -i - -O $DATADIR/geoip.dat
|
|
||||||
|
|
||||||
wget -qO - https://api.github.com/repos/v2ray/domain-list-community/releases/latest \
|
|
||||||
| grep browser_download_url | cut -d '"' -f 4 \
|
|
||||||
| wget -i - -O $DATADIR/geosite.dat
|
|
||||||
}
|
|
||||||
|
|
||||||
ACTION="${1:-}"
|
|
||||||
if [[ -z $ACTION ]]; then
|
|
||||||
ACTION=download
|
|
||||||
fi
|
|
||||||
|
|
||||||
case $ACTION in
|
|
||||||
"download") download_dat;;
|
|
||||||
"compile") compile_dat;;
|
|
||||||
esac
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
module github.com/2dust/AndroidLibV2rayLite
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/xtls/xray-core v1.4.2
|
|
||||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08
|
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
|
|
||||||
)
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
|
||||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
|
||||||
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
|
||||||
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
|
||||||
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
|
||||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
|
||||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
|
||||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
|
|
||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
|
||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
|
||||||
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
|
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
|
||||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/lucas-clemente/quic-go v0.20.0/go.mod h1:fZq/HUDIM+mW6X6wtzORjC0E/WDBMKe5Hf9bgjISwLk=
|
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
|
||||||
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
|
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.3/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
|
||||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
|
||||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
|
||||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
|
||||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
|
||||||
github.com/pires/go-proxyproto v0.5.0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/refraction-networking/utls v0.0.0-20201210053706-2179f286686b/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
|
|
||||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
|
||||||
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
|
||||||
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
|
||||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
|
||||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
|
||||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
|
||||||
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
|
||||||
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
|
||||||
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
|
||||||
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
|
||||||
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
|
||||||
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
|
||||||
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
|
||||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
|
||||||
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
|
||||||
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
|
||||||
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
|
||||||
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
|
||||||
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
|
||||||
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
|
||||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
|
||||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
|
||||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
|
||||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
|
||||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
|
||||||
github.com/xtls/go v0.0.0-20201118062508-3632bf3b7499/go.mod h1:5TB2+k58gx4A4g2Nf5miSHNDF6CuAzHKpWBooLAshTs=
|
|
||||||
github.com/xtls/xray-core v1.4.2 h1:D0Le+Qy9L/eY5LbUQfrk7WJ8wbODpQSW/ZRCg+BRe7c=
|
|
||||||
github.com/xtls/xray-core v1.4.2/go.mod h1:DmL/9rOCliev/a6HciWEvSJVEhUF6C0EpD3clW8v0pc=
|
|
||||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
|
||||||
go.starlark.net v0.0.0-20210312235212-74c10e2c17dc/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
|
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
|
||||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
|
|
||||||
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/net v0.0.0-20210330230544-e57232859fb2/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201231184435-2d18734c6014/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
|
||||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
|
||||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
|
||||||
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
|
||||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
|
||||||
h12.io/socks v1.0.2/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
|
||||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
package libv2ray
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
|
||||||
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
|
|
||||||
"github.com/2dust/AndroidLibV2rayLite/VPN"
|
|
||||||
mobasset "golang.org/x/mobile/asset"
|
|
||||||
|
|
||||||
v2core "github.com/xtls/xray-core/core"
|
|
||||||
v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
|
|
||||||
v2stats "github.com/xtls/xray-core/features/stats"
|
|
||||||
v2serial "github.com/xtls/xray-core/infra/conf/serial"
|
|
||||||
_ "github.com/xtls/xray-core/main/distro/all"
|
|
||||||
v2internet "github.com/xtls/xray-core/transport/internet"
|
|
||||||
|
|
||||||
v2applog "github.com/xtls/xray-core/app/log"
|
|
||||||
v2commlog "github.com/xtls/xray-core/common/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
v2Assert = "xray.location.asset"
|
|
||||||
assetperfix = "/dev/libv2rayfs0/asset"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*V2RayPoint V2Ray Point Server
|
|
||||||
This is territory of Go, so no getter and setters!
|
|
||||||
*/
|
|
||||||
type V2RayPoint struct {
|
|
||||||
SupportSet V2RayVPNServiceSupportsSet
|
|
||||||
statsManager v2stats.Manager
|
|
||||||
|
|
||||||
dialer *VPN.ProtectedDialer
|
|
||||||
status *CoreI.Status
|
|
||||||
escorter *Escort.Escorting
|
|
||||||
v2rayOP *sync.Mutex
|
|
||||||
closeChan chan struct{}
|
|
||||||
|
|
||||||
PackageName string
|
|
||||||
PackageCodePath string
|
|
||||||
DomainName string
|
|
||||||
ConfigureFileContent string
|
|
||||||
EnableLocalDNS bool
|
|
||||||
ForwardIpv6 bool
|
|
||||||
ProxyOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
|
||||||
type V2RayVPNServiceSupportsSet interface {
|
|
||||||
Setup(Conf string) int
|
|
||||||
Prepare() int
|
|
||||||
Shutdown() int
|
|
||||||
Protect(int) int
|
|
||||||
OnEmitStatus(int, string) int
|
|
||||||
}
|
|
||||||
|
|
||||||
/*RunLoop Run V2Ray main loop
|
|
||||||
*/
|
|
||||||
func (v *V2RayPoint) RunLoop() (err error) {
|
|
||||||
v.v2rayOP.Lock()
|
|
||||||
defer v.v2rayOP.Unlock()
|
|
||||||
//Construct Context
|
|
||||||
v.status.PackageName = v.PackageName
|
|
||||||
v.status.PackageCodePath = v.PackageCodePath
|
|
||||||
|
|
||||||
if !v.status.IsRunning {
|
|
||||||
v.closeChan = make(chan struct{})
|
|
||||||
v.dialer.PrepareResolveChan()
|
|
||||||
go v.dialer.PrepareDomain(v.DomainName, v.closeChan)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
// wait until resolved
|
|
||||||
case <-v.dialer.ResolveChan():
|
|
||||||
// shutdown VPNService if server name can not reolved
|
|
||||||
if !v.dialer.IsVServerReady() {
|
|
||||||
log.Println("vServer cannot resolved, shutdown")
|
|
||||||
v.StopLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop waiting if manually closed
|
|
||||||
case <-v.closeChan:
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err = v.pointloop()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
/*StopLoop Stop V2Ray main loop
|
|
||||||
*/
|
|
||||||
func (v *V2RayPoint) StopLoop() (err error) {
|
|
||||||
v.v2rayOP.Lock()
|
|
||||||
defer v.v2rayOP.Unlock()
|
|
||||||
if v.status.IsRunning {
|
|
||||||
v.shutdownInit()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delegate Funcation
|
|
||||||
func (v *V2RayPoint) GetIsRunning() bool {
|
|
||||||
return v.status.IsRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2RayPoint) GetIsTRunning() bool {
|
|
||||||
return v.status.IsTRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delegate Funcation
|
|
||||||
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
|
||||||
if v.statsManager == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
counter := v.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
|
|
||||||
if counter == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return counter.Set(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2RayPoint) shutdownInit() {
|
|
||||||
close(v.closeChan)
|
|
||||||
v.statsManager = nil
|
|
||||||
v.status.Vpoint.Close()
|
|
||||||
v.status.Vpoint = nil
|
|
||||||
v.status.IsRunning = false
|
|
||||||
|
|
||||||
v.escorter.EscortingDown()
|
|
||||||
|
|
||||||
v.SupportSet.Shutdown()
|
|
||||||
v.SupportSet.OnEmitStatus(0, "Closed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *V2RayPoint) pointloop() error {
|
|
||||||
log.Println("loading v2ray config")
|
|
||||||
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("new v2ray core")
|
|
||||||
inst, err := v2core.New(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.status.Vpoint = inst
|
|
||||||
v.statsManager = inst.GetFeature(v2stats.ManagerType()).(v2stats.Manager)
|
|
||||||
|
|
||||||
log.Println("start v2ray core")
|
|
||||||
v.status.IsRunning = true
|
|
||||||
if err := v.status.Vpoint.Start(); err != nil {
|
|
||||||
v.status.IsRunning = false
|
|
||||||
log.Println(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func initV2Env() {
|
|
||||||
if os.Getenv(v2Assert) != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
//Initialize asset API, Since Raymond Will not let notify the asset location inside Process,
|
|
||||||
//We need to set location outside V2Ray
|
|
||||||
os.Setenv(v2Assert, assetperfix)
|
|
||||||
//Now we handle read
|
|
||||||
v2filesystem.NewFileReader = func(path string) (io.ReadCloser, error) {
|
|
||||||
if strings.HasPrefix(path, assetperfix) {
|
|
||||||
p := path[len(assetperfix)+1:]
|
|
||||||
//is it overridden?
|
|
||||||
//by, ok := overridedAssets[p]
|
|
||||||
//if ok {
|
|
||||||
// return os.Open(by)
|
|
||||||
//}
|
|
||||||
return mobasset.Open(p)
|
|
||||||
}
|
|
||||||
return os.Open(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Delegate Funcation
|
|
||||||
func TestConfig(ConfigureFileContent string) error {
|
|
||||||
initV2Env()
|
|
||||||
_, err := v2serial.LoadJSONConfig(strings.NewReader(ConfigureFileContent))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*NewV2RayPoint new V2RayPoint*/
|
|
||||||
func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
|
|
||||||
initV2Env()
|
|
||||||
|
|
||||||
// inject our own log writer
|
|
||||||
v2applog.RegisterHandlerCreator(v2applog.LogType_Console,
|
|
||||||
func(lt v2applog.LogType,
|
|
||||||
options v2applog.HandlerCreatorOptions) (v2commlog.Handler, error) {
|
|
||||||
return v2commlog.NewLogger(createStdoutLogWriter()), nil
|
|
||||||
})
|
|
||||||
|
|
||||||
dialer := VPN.NewPreotectedDialer(s)
|
|
||||||
v2internet.UseAlternativeSystemDialer(dialer)
|
|
||||||
status := &CoreI.Status{}
|
|
||||||
return &V2RayPoint{
|
|
||||||
SupportSet: s,
|
|
||||||
v2rayOP: new(sync.Mutex),
|
|
||||||
status: status,
|
|
||||||
dialer: dialer,
|
|
||||||
escorter: &Escort.Escorting{Status: status},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v V2RayPoint) runTun2socks() error {
|
|
||||||
v.escorter.EscortingUp()
|
|
||||||
go v.escorter.EscortRun(
|
|
||||||
v.status.GetApp("libtun2socks.so"),
|
|
||||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*CheckVersion int
|
|
||||||
This func will return libv2ray binding version.
|
|
||||||
*/
|
|
||||||
func CheckVersion() int {
|
|
||||||
return CoreI.CheckVersion()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*CheckVersionX string
|
|
||||||
This func will return libv2ray binding version and V2Ray version used.
|
|
||||||
*/
|
|
||||||
func CheckVersionX() string {
|
|
||||||
return fmt.Sprintf("Libv2rayLite V%d, Core V%s", CheckVersion(), v2core.Version())
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
package libv2ray
|
|
||||||
|
|
||||||
//go:generate make all
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
readme.txt
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Copyright (C) 2009 The Android Open Source Project
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
ROOT_PATH := $(LOCAL_PATH)
|
|
||||||
|
|
||||||
########################################################
|
|
||||||
## libancillary
|
|
||||||
########################################################
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
|
|
||||||
ANCILLARY_SOURCE := fd_recv.c fd_send.c
|
|
||||||
|
|
||||||
LOCAL_MODULE := libancillary
|
|
||||||
#LOCAL_CFLAGS += -I$(LOCAL_PATH)/libancillary
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/libancillary
|
|
||||||
LOCAL_SRC_FILES := $(addprefix libancillary/, $(ANCILLARY_SOURCE))
|
|
||||||
|
|
||||||
include $(BUILD_STATIC_LIBRARY)
|
|
||||||
|
|
||||||
########################################################
|
|
||||||
## tun2socks
|
|
||||||
########################################################
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
|
|
||||||
LOCAL_CFLAGS := -std=gnu99
|
|
||||||
LOCAL_CFLAGS += -DBADVPN_THREADWORK_USE_PTHREAD -DBADVPN_LINUX -DBADVPN_BREACTOR_BADVPN -D_GNU_SOURCE
|
|
||||||
LOCAL_CFLAGS += -DBADVPN_USE_SIGNALFD -DBADVPN_USE_EPOLL
|
|
||||||
LOCAL_CFLAGS += -DBADVPN_LITTLE_ENDIAN -DBADVPN_THREAD_SAFE
|
|
||||||
LOCAL_CFLAGS += -DNDEBUG -DANDROID
|
|
||||||
LOCAL_CFLAGS += -I
|
|
||||||
|
|
||||||
LOCAL_STATIC_LIBRARIES := libancillary
|
|
||||||
|
|
||||||
LOCAL_C_INCLUDES := \
|
|
||||||
$(LOCAL_PATH)/badvpn/libancillary \
|
|
||||||
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv4 \
|
|
||||||
$(LOCAL_PATH)/badvpn/lwip/src/include/ipv6 \
|
|
||||||
$(LOCAL_PATH)/badvpn/lwip/src/include \
|
|
||||||
$(LOCAL_PATH)/badvpn/lwip/custom \
|
|
||||||
$(LOCAL_PATH)/badvpn \
|
|
||||||
$(LOCAL_PATH)/libancillary
|
|
||||||
|
|
||||||
TUN2SOCKS_SOURCES := \
|
|
||||||
base/BLog_syslog.c \
|
|
||||||
system/BReactor_badvpn.c \
|
|
||||||
system/BSignal.c \
|
|
||||||
system/BConnection_common.c \
|
|
||||||
system/BConnection_unix.c \
|
|
||||||
system/BTime.c \
|
|
||||||
system/BUnixSignal.c \
|
|
||||||
system/BNetwork.c \
|
|
||||||
flow/StreamRecvInterface.c \
|
|
||||||
flow/PacketRecvInterface.c \
|
|
||||||
flow/PacketPassInterface.c \
|
|
||||||
flow/StreamPassInterface.c \
|
|
||||||
flow/SinglePacketBuffer.c \
|
|
||||||
flow/BufferWriter.c \
|
|
||||||
flow/PacketBuffer.c \
|
|
||||||
flow/PacketStreamSender.c \
|
|
||||||
flow/PacketPassConnector.c \
|
|
||||||
flow/PacketProtoFlow.c \
|
|
||||||
flow/PacketPassFairQueue.c \
|
|
||||||
flow/PacketProtoEncoder.c \
|
|
||||||
flow/PacketProtoDecoder.c \
|
|
||||||
socksclient/BSocksClient.c \
|
|
||||||
tuntap/BTap.c \
|
|
||||||
lwip/src/core/udp.c \
|
|
||||||
lwip/src/core/memp.c \
|
|
||||||
lwip/src/core/init.c \
|
|
||||||
lwip/src/core/pbuf.c \
|
|
||||||
lwip/src/core/tcp.c \
|
|
||||||
lwip/src/core/tcp_out.c \
|
|
||||||
lwip/src/core/netif.c \
|
|
||||||
lwip/src/core/def.c \
|
|
||||||
lwip/src/core/ip.c \
|
|
||||||
lwip/src/core/mem.c \
|
|
||||||
lwip/src/core/tcp_in.c \
|
|
||||||
lwip/src/core/stats.c \
|
|
||||||
lwip/src/core/inet_chksum.c \
|
|
||||||
lwip/src/core/timeouts.c \
|
|
||||||
lwip/src/core/ipv4/icmp.c \
|
|
||||||
lwip/src/core/ipv4/igmp.c \
|
|
||||||
lwip/src/core/ipv4/ip4_addr.c \
|
|
||||||
lwip/src/core/ipv4/ip4_frag.c \
|
|
||||||
lwip/src/core/ipv4/ip4.c \
|
|
||||||
lwip/src/core/ipv4/autoip.c \
|
|
||||||
lwip/src/core/ipv6/ethip6.c \
|
|
||||||
lwip/src/core/ipv6/inet6.c \
|
|
||||||
lwip/src/core/ipv6/ip6_addr.c \
|
|
||||||
lwip/src/core/ipv6/mld6.c \
|
|
||||||
lwip/src/core/ipv6/dhcp6.c \
|
|
||||||
lwip/src/core/ipv6/icmp6.c \
|
|
||||||
lwip/src/core/ipv6/ip6.c \
|
|
||||||
lwip/src/core/ipv6/ip6_frag.c \
|
|
||||||
lwip/src/core/ipv6/nd6.c \
|
|
||||||
lwip/custom/sys.c \
|
|
||||||
tun2socks/tun2socks.c \
|
|
||||||
base/DebugObject.c \
|
|
||||||
base/BLog.c \
|
|
||||||
base/BPending.c \
|
|
||||||
system/BDatagram_unix.c \
|
|
||||||
flowextra/PacketPassInactivityMonitor.c \
|
|
||||||
tun2socks/SocksUdpGwClient.c \
|
|
||||||
udpgw_client/UdpGwClient.c
|
|
||||||
|
|
||||||
LOCAL_MODULE := tun2socks
|
|
||||||
|
|
||||||
LOCAL_LDLIBS := -ldl -llog
|
|
||||||
|
|
||||||
LOCAL_SRC_FILES := $(addprefix badvpn/, $(TUN2SOCKS_SOURCES))
|
|
||||||
|
|
||||||
include $(BUILD_SYSTEM)/build-executable.mk
|
|
||||||
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Thanks to https://gist.github.com/wenzhixin/43cf3ce909c24948c6e7
|
|
||||||
# Execute this script in your home directory. Lines 17 and 21 will prompt you for a y/n
|
|
||||||
|
|
||||||
# Install Oracle JDK 8
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y openjdk-8-jdk
|
|
||||||
apt-get install -y unzip make expect # NDK stuff
|
|
||||||
|
|
||||||
# Get SDK tools (link from https://developer.android.com/studio/index.html#downloads)
|
|
||||||
wget -q https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
|
|
||||||
mkdir android-sdk-linux
|
|
||||||
unzip sdk*.zip -d android-sdk-linux
|
|
||||||
|
|
||||||
# Get NDK (https://developer.android.com/ndk/downloads/index.html)
|
|
||||||
# wget -q https://dl.google.com/android/repository/android-ndk-r15c-linux-x86_64.zip
|
|
||||||
# unzip android-ndk*.zip >> /dev/null
|
|
||||||
|
|
||||||
ACCEPT_LICENSES_URL=https://gist.githubusercontent.com/xiaokangwang/1489fd223d26581bfec92adb3cb0088e/raw/328eb6925099df5aae3e76790f8232f0fc378f8b/accept-licenses
|
|
||||||
|
|
||||||
ACCEPT_LICENSES_ITEM="android-sdk-license-bcbbd656|intel-android-sysimage-license-1ea702d1|android-sdk-license-2742d1c5"
|
|
||||||
|
|
||||||
# Let it update itself and install some stuff
|
|
||||||
cd android-sdk-linux/tools
|
|
||||||
|
|
||||||
curl -L -o accept-licenses $ACCEPT_LICENSES_URL
|
|
||||||
|
|
||||||
chmod +x accept-licenses
|
|
||||||
|
|
||||||
./accept-licenses "./android update sdk --use-sdk-wrapper --all --no-ui" $ACCEPT_LICENSES_ITEM >/dev/null
|
|
||||||
|
|
||||||
# Download every build-tools version that has ever existed
|
|
||||||
# This will save you time! Thank me later for this
|
|
||||||
|
|
||||||
#./accept-licenses "./android update sdk --use-sdk-wrapper --all --no-ui --filter 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27" $ACCEPT_LICENSES_ITEM
|
|
||||||
|
|
||||||
PACKAGE_PARSE_URL=https://gist.githubusercontent.com/xiaokangwang/06268fb23034ed94bc301880e862da09/raw/afd95cbbe2f8c1d9e7b0277b7c5ef39af756a6ee/parse.awk
|
|
||||||
|
|
||||||
reduceout=https://gist.githubusercontent.com/xiaokangwang/4684bdb5c3415b943f52aa4803386480/raw/b46dab1cc60f02c0d87f88f01e27157034218faa/out.awk
|
|
||||||
|
|
||||||
cd bin
|
|
||||||
|
|
||||||
curl -L -o parse.awk $PACKAGE_PARSE_URL
|
|
||||||
|
|
||||||
curl -L -o reduce.awk $reduceout
|
|
||||||
|
|
||||||
sudo apt-get install gawk
|
|
||||||
|
|
||||||
./sdkmanager --verbose --list |awk -f parse.awk > ~/package_to_install
|
|
||||||
|
|
||||||
readarray -t filenames < $HOME/package_to_install
|
|
||||||
|
|
||||||
cat $HOME/package_to_install
|
|
||||||
|
|
||||||
yes|./sdkmanager --verbose "${filenames[@]}" |awk -f reduce.awk
|
|
||||||
|
|
||||||
# If you need additional packages for your app, check available packages with:
|
|
||||||
# ./android list sdk --all
|
|
||||||
|
|
||||||
# install certain packages with:
|
|
||||||
# ./android update sdk --no-ui --all --filter 1,2,3,<...>,N
|
|
||||||
# where N is the number of the package in the list (see previous command)
|
|
||||||
|
|
||||||
./sdkmanager "ndk-bundle"
|
|
||||||
|
|
||||||
# Add the directory containing executables in PATH so that they can be found
|
|
||||||
echo 'export ANDROID_HOME=$HOME/android-sdk-linux' >> ~/.bashrc
|
|
||||||
echo 'export PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools' >> ~/.bashrc
|
|
||||||
# echo 'export NDK_HOME=$HOME/android-ndk-r15c' >> ~/.bashrc
|
|
||||||
# echo 'export ANDROID_NDK_HOME=$NDK_HOME' >> ~/.bashrc
|
|
||||||
|
|
||||||
|
|
||||||
source ~/.bashrc
|
|
||||||
|
|
||||||
# Make sure you can execute 32 bit executables if this is 64 bit machine, otherwise skip this
|
|
||||||
dpkg --add-architecture i386
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y libc6:i386 libstdc++6:i386 zlib1g:i386
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package libv2ray
|
|
||||||
|
|
||||||
// This struct creates our own log writer without datatime stamp
|
|
||||||
// As Android adds time stamps on each line
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
v2commlog "github.com/xtls/xray-core/common/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type consoleLogWriter struct {
|
|
||||||
logger *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *consoleLogWriter) Write(s string) error {
|
|
||||||
w.logger.Print(s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *consoleLogWriter) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This logger won't print data/time stamps
|
|
||||||
func createStdoutLogWriter() v2commlog.WriterCreator {
|
|
||||||
return func() v2commlog.Writer {
|
|
||||||
return &consoleLogWriter{
|
|
||||||
logger: log.New(os.Stdout, "", 0)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
README.md
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
|
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://developer.android.com/about/versions/lollipop)
|
||||||
[](https://kotlinlang.org)
|
[](https://kotlinlang.org)
|
||||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||||
[](https://github.com/2dust/v2rayNG/releases)
|
[](https://github.com/2dust/v2rayNG/releases)
|
||||||
@@ -16,20 +16,18 @@ A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-cor
|
|||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
#### Geoip and Geosite
|
#### Geoip and Geosite
|
||||||
v2rayNG release already embedded domain file `geoip.dat` and `geosite.dat`. However it is (probably) not the latest and not the most complete list.
|
- geoip.dat and geosite.dat files are in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
|
||||||
For power user, the embedded files can be easily replaced with the following steps:
|
- download feature will get enhanced version in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (Note it need a working proxy)
|
||||||
1. Launch v2rayNG (v1.4.9+)
|
- latest official [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip) can be imported manually
|
||||||
2. Find existing geoip.dat and geosite.dat in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
|
- 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)
|
||||||
3. Replace them with the latest [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip)
|
|
||||||
4. Enhanced version can be found in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (recommend to use `geosite:geolocation-!cn` for proxy dns and routing)
|
|
||||||
5. It is also possible to use third party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
|
|
||||||
|
|
||||||
#### See more in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
### More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||||
|
|
||||||
### Development guide
|
### Development guide
|
||||||
|
|
||||||
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
|
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
|
||||||
The aar can be compiled from the Golang project under AndroidLibV2rayLite folder. For a quick start, read guide for [Go Mobile](https://github.com/golang/go/wiki/Mobile)
|
The aar can be compiled from the Golang project [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite).
|
||||||
and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)
|
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
V2rayNG/.gitignore
vendored
@@ -7,3 +7,4 @@
|
|||||||
/captures
|
/captures
|
||||||
*.apk
|
*.apk
|
||||||
signing.properties
|
signing.properties
|
||||||
|
*.aar
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
|
Properties props = new Properties()
|
||||||
|
props.load(new FileInputStream(new File('local.properties')))
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||||
buildToolsVersion buildToolsVer
|
buildToolsVersion "$buildToolsVer"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
targetCompatibility = "8"
|
targetCompatibility = "8"
|
||||||
@@ -13,11 +15,28 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.v2ray.ang"
|
applicationId "com.v2ray.ang"
|
||||||
minSdkVersion 17
|
minSdkVersion 21
|
||||||
targetSdkVersion Integer.parseInt("$targetSdkVer")
|
targetSdkVersion Integer.parseInt("$targetSdkVer")
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
versionCode 212
|
versionCode 472
|
||||||
versionName "1.0.2"
|
versionName "1.7.18"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props["sign"]) {
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storeFile file("../key.jks")
|
||||||
|
keyAlias 'ang'
|
||||||
|
keyPassword '123456'
|
||||||
|
storePassword '123456'
|
||||||
|
}
|
||||||
|
debug {
|
||||||
|
storeFile file("../key.jks")
|
||||||
|
keyAlias 'ang'
|
||||||
|
keyPassword '123456'
|
||||||
|
storePassword '123456'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -25,6 +44,9 @@ android {
|
|||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
zipAlignEnabled false
|
zipAlignEnabled false
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
|
if (props["sign"]) {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
@@ -32,12 +54,18 @@ android {
|
|||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
zipAlignEnabled false
|
zipAlignEnabled false
|
||||||
shrinkResources false
|
shrinkResources false
|
||||||
|
if (props["sign"]) {
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main {
|
||||||
|
jniLibs.srcDirs = ['libs']
|
||||||
|
java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
@@ -59,6 +87,8 @@ android {
|
|||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
// assign different version code for each output
|
// assign different version code for each output
|
||||||
variant.outputs.each { output ->
|
variant.outputs.each { output ->
|
||||||
|
output.outputFileName = "v2rayNG_" + variant.versionName + "_" + output.getFilter(com.android.build.OutputFile.ABI) + ".apk"
|
||||||
|
|
||||||
output.versionCodeOverride =
|
output.versionCodeOverride =
|
||||||
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
|
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
|
||||||
1000000 + android.defaultConfig.versionCode
|
1000000 + android.defaultConfig.versionCode
|
||||||
@@ -72,32 +102,33 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
|
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
|
|
||||||
// Androidx
|
// Androidx
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation "androidx.legacy:legacy-support-v4:1.0.0"
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation "androidx.appcompat:appcompat:1.3.0"
|
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||||
implementation "com.google.android.material:material:1.4.0"
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation "androidx.preference:preference:1.0.0"
|
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
|
implementation 'androidx.fragment:fragment-ktx:1.5.0'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||||
|
|
||||||
// Androidx ktx
|
// Androidx ktx
|
||||||
implementation 'androidx.activity:activity-ktx:1.2.4'
|
implementation 'androidx.activity:activity-ktx:1.5.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0'
|
||||||
|
|
||||||
//kotlin
|
//kotlin
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
|
||||||
|
|
||||||
implementation 'com.tencent:mmkv-static:1.2.7'
|
implementation 'com.tencent:mmkv-static:1.2.12'
|
||||||
implementation 'com.google.code.gson:gson:2.8.6'
|
implementation 'com.google.code.gson:gson:2.8.9'
|
||||||
implementation 'io.reactivex:rxjava:1.3.4'
|
implementation 'io.reactivex:rxjava:1.3.4'
|
||||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||||
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
|
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
|
||||||
@@ -105,17 +136,17 @@ dependencies {
|
|||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
||||||
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||||
implementation 'com.blacksquircle.ui:editorkit:2.0.0'
|
implementation 'com.blacksquircle.ui:editorkit:2.1.1'
|
||||||
implementation 'com.blacksquircle.ui:language-json:2.0.0'
|
implementation 'com.blacksquircle.ui:language-base:2.1.1'
|
||||||
|
implementation 'com.blacksquircle.ui:language-json:2.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
maven { url 'https://maven.google.com' }
|
maven { url 'https://maven.google.com' }
|
||||||
}
|
maven { url 'https://jitpack.io' }
|
||||||
dependencies {
|
jcenter()
|
||||||
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
https://github.com/2dust/v2rayNG/tree/master/AndroidLibV2rayLite
|
|
||||||
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so → V2rayNG/app/libs/arm64-v8a/libtun2socks.so
Executable file → Normal file
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so → V2rayNG/app/libs/armeabi-v7a/libtun2socks.so
Executable file → Normal file
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so → V2rayNG/app/libs/x86/libtun2socks.so
Executable file → Normal file
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so → V2rayNG/app/libs/x86_64/libtun2socks.so
Executable file → Normal file
58
V2rayNG/app/proguard-rules.pro
vendored
@@ -1,58 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# By default, the flags in this file are appended to flags specified
|
|
||||||
# in G:\android-sdk/tools/proguard/proguard-android.txt
|
|
||||||
# You can edit the include path and order by changing the proguardFiles
|
|
||||||
# directive in build.gradle.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# Add any project specific keep options here:
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Gson uses generic type information stored in a class file when working with fields. Proguard
|
|
||||||
# removes such information by default, so configure it to keep all of it.
|
|
||||||
-keepattributes Signature
|
|
||||||
|
|
||||||
# For using GSON @Expose annotation
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
# Gson specific classes
|
|
||||||
-keep class sun.misc.Unsafe { *; }
|
|
||||||
|
|
||||||
-dontwarn org.apache.commons.**
|
|
||||||
-keep class org.apache.commons.** { *;}
|
|
||||||
|
|
||||||
# Disable debug info output
|
|
||||||
-assumenosideeffects class android.util.Log {
|
|
||||||
public static boolean isLoggable(java.lang.String,int);
|
|
||||||
public static int v(...);
|
|
||||||
public static int i(...);
|
|
||||||
public static int w(...);
|
|
||||||
public static int d(...);
|
|
||||||
public static int e(...);
|
|
||||||
}
|
|
||||||
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
|
|
||||||
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
|
|
||||||
static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);
|
|
||||||
static void throwUninitializedPropertyAccessException(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
-dontwarn rx.internal.util.unsafe.**
|
|
||||||
-keep class rx.internal.util.unsafe.** { *;}
|
|
||||||
|
|
||||||
-dontwarn app.dinus.**
|
|
||||||
-keep class app.dinus.** { *;}
|
|
||||||
|
|
||||||
-keepclassmembers class ** {
|
|
||||||
@com.hwangjr.rxbus.annotation.Subscribe public *;
|
|
||||||
@com.hwangjr.rxbus.annotation.Produce public *;
|
|
||||||
}
|
|
||||||
|
|
||||||
-keep class libv2ray.** { *;}
|
|
||||||
|
|||||||
@@ -13,44 +13,35 @@
|
|||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||||
|
|
||||||
|
|
||||||
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
||||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
tools:ignore="QueryAllPackagesPermission" />
|
tools:ignore="QueryAllPackagesPermission" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
|
<!-- <useapplications-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> -->
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="com.android.vending.BILLING" />
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||||
|
|
||||||
<uses-sdk tools:overrideLibrary="com.blacksquircle.ui.editorkit, com.blacksquircle.ui.language.json, com.blacksquircle.ui.language.base"/>
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".AngApplication"
|
android:name=".AngApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:extractNativeLibs="true"
|
android:theme="@style/AppThemeLight"
|
||||||
android:theme="@style/AppTheme">
|
android:usesCleartextTraffic="true"
|
||||||
|
tools:targetApi="m">
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="true"
|
||||||
android:name=".ui.MainActivity"
|
android:name=".ui.MainActivity"
|
||||||
android:theme="@style/AppTheme.NoActionBar"
|
|
||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:mimeType="text/plain" />
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
@@ -59,29 +50,66 @@
|
|||||||
android:resource="@xml/shortcuts" />
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="false"
|
||||||
android:name=".ui.ServerActivity"
|
android:name=".ui.ServerActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
android:windowSoftInputMode="stateUnchanged" />
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="false"
|
||||||
android:name=".ui.ServerCustomConfigActivity"
|
android:name=".ui.ServerCustomConfigActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
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" />
|
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="false"
|
||||||
|
android:name=".ui.SettingsActivity" />
|
||||||
|
<activity
|
||||||
|
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:name=".ui.RoutingSettingsActivity"
|
||||||
android:windowSoftInputMode="stateUnchanged" />
|
android:windowSoftInputMode="stateUnchanged" />
|
||||||
<activity android:name=".ui.SubSettingActivity" />
|
|
||||||
|
|
||||||
<activity android:name=".ui.SubEditActivity" />
|
|
||||||
<activity android:name=".ui.ScScannerActivity" />
|
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="false"
|
||||||
|
android:name=".ui.SubSettingActivity" />
|
||||||
|
<activity
|
||||||
|
android:exported="false"
|
||||||
|
android:name=".ui.UserAssetActivity" />
|
||||||
|
|
||||||
|
<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:name=".ui.ScSwitchActivity"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:process=":RunSoLibV2RayDaemon"
|
android:process=":RunSoLibV2RayDaemon"
|
||||||
android:theme="@style/AppTheme.NoActionBar.Translucent" />
|
android:theme="@style/AppTheme.NoActionBar.Translucent" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:exported="true"
|
||||||
|
android:name=".ui.UrlSchemeActivity">
|
||||||
|
<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.intent.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:scheme="v2rayng"
|
||||||
|
android:host="install-config" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.V2RayVpnService"
|
android:name=".service.V2RayVpnService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -103,7 +131,14 @@
|
|||||||
android:process=":RunSoLibV2RayDaemon">
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver android:name=".receiver.WidgetProvider"
|
<service android:name=".service.V2RayTestService"
|
||||||
|
android:exported="false"
|
||||||
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:exported="true"
|
||||||
|
android:name=".receiver.WidgetProvider"
|
||||||
android:process=":RunSoLibV2RayDaemon">
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.appwidget.provider"
|
android:name="android.appwidget.provider"
|
||||||
@@ -116,8 +151,9 @@
|
|||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
android:exported="true"
|
||||||
android:name=".service.QSTileService"
|
android:name=".service.QSTileService"
|
||||||
android:icon="@drawable/ic_v"
|
android:icon="@drawable/ic_stat_name"
|
||||||
android:label="@string/app_tile_name"
|
android:label="@string/app_tile_name"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
android:process=":RunSoLibV2RayDaemon">
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
@@ -127,6 +163,7 @@
|
|||||||
</service>
|
</service>
|
||||||
<!-- =====================Tasker===================== -->
|
<!-- =====================Tasker===================== -->
|
||||||
<activity
|
<activity
|
||||||
|
android:exported="true"
|
||||||
android:name=".ui.TaskerActivity"
|
android:name=".ui.TaskerActivity"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name">
|
||||||
@@ -135,7 +172,9 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<receiver android:name=".receiver.TaskerReceiver"
|
<receiver
|
||||||
|
android:exported="true"
|
||||||
|
android:name=".receiver.TaskerReceiver"
|
||||||
android:process=":RunSoLibV2RayDaemon">
|
android:process=":RunSoLibV2RayDaemon">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2012 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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.android.vending.billing;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* InAppBillingService is the service that provides in-app billing version 3 and beyond.
|
|
||||||
* This service provides the following features:
|
|
||||||
* 1. Provides a new API to get details of in-app items published for the app including
|
|
||||||
* price, type, title and description.
|
|
||||||
* 2. The purchase flow is synchronous and purchase information is available immediately
|
|
||||||
* after it completes.
|
|
||||||
* 3. Purchase information of in-app purchases is maintained within the Google Play system
|
|
||||||
* till the purchase is consumed.
|
|
||||||
* 4. An API to consume a purchase of an inapp item. All purchases of one-time
|
|
||||||
* in-app items are consumable and thereafter can be purchased again.
|
|
||||||
* 5. An API to get current purchases of the user immediately. This will not contain any
|
|
||||||
* consumed purchases.
|
|
||||||
*
|
|
||||||
* All calls will give a response code with the following possible values
|
|
||||||
* RESULT_OK = 0 - success
|
|
||||||
* RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
|
|
||||||
* RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
|
|
||||||
* RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
|
|
||||||
* RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
|
|
||||||
* RESULT_ERROR = 6 - Fatal error during the API action
|
|
||||||
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
|
|
||||||
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
|
|
||||||
*/
|
|
||||||
interface IInAppBillingService {
|
|
||||||
/**
|
|
||||||
* Checks support for the requested billing API version, package and in-app type.
|
|
||||||
* Minimum API version supported by this interface is 3.
|
|
||||||
* @param apiVersion the billing version which the app is using
|
|
||||||
* @param packageName the package name of the calling app
|
|
||||||
* @param type type of the in-app item being purchased "inapp" for one-time purchases
|
|
||||||
* and "subs" for subscription.
|
|
||||||
* @return RESULT_OK(0) on success, corresponding result code on failures
|
|
||||||
*/
|
|
||||||
int isBillingSupported(int apiVersion, String packageName, String type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides details of a list of SKUs
|
|
||||||
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
|
|
||||||
* with a list JSON strings containing the productId, price, title and description.
|
|
||||||
* This API can be called with a maximum of 20 SKUs.
|
|
||||||
* @param apiVersion billing API version that the Third-party is using
|
|
||||||
* @param packageName the package name of the calling app
|
|
||||||
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
|
|
||||||
* @return Bundle containing the following key-value pairs
|
|
||||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
|
||||||
* failure as listed above.
|
|
||||||
* "DETAILS_LIST" with a StringArrayList containing purchase information
|
|
||||||
* in JSON format similar to:
|
|
||||||
* '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
|
|
||||||
* "title : "Example Title", "description" : "This is an example description" }'
|
|
||||||
*/
|
|
||||||
Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
|
|
||||||
* the type, a unique purchase token and an optional developer payload.
|
|
||||||
* @param apiVersion billing API version that the app is using
|
|
||||||
* @param packageName package name of the calling app
|
|
||||||
* @param sku the SKU of the in-app item as published in the developer console
|
|
||||||
* @param type the type of the in-app item ("inapp" for one-time purchases
|
|
||||||
* and "subs" for subscription).
|
|
||||||
* @param developerPayload optional argument to be sent back with the purchase information
|
|
||||||
* @return Bundle containing the following key-value pairs
|
|
||||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
|
||||||
* failure as listed above.
|
|
||||||
* "BUY_INTENT" - PendingIntent to start the purchase flow
|
|
||||||
*
|
|
||||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow
|
|
||||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
|
|
||||||
* If the purchase is successful, the result data will contain the following key-value pairs
|
|
||||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
|
||||||
* failure as listed above.
|
|
||||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to
|
|
||||||
* '{"orderId":"12999763169054705758.1371079406387615",
|
|
||||||
* "packageName":"com.example.app",
|
|
||||||
* "productId":"exampleSku",
|
|
||||||
* "purchaseTime":1345678900000,
|
|
||||||
* "purchaseToken" : "122333444455555",
|
|
||||||
* "developerPayload":"example developer payload" }'
|
|
||||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
|
|
||||||
* was signed with the private key of the developer
|
|
||||||
* TODO: change this to app-specific keys.
|
|
||||||
*/
|
|
||||||
Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
|
|
||||||
String developerPayload);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current SKUs owned by the user of the type and package name specified along with
|
|
||||||
* purchase information and a signature of the data to be validated.
|
|
||||||
* This will return all SKUs that have been purchased in V3 and managed items purchased using
|
|
||||||
* V1 and V2 that have not been consumed.
|
|
||||||
* @param apiVersion billing API version that the app is using
|
|
||||||
* @param packageName package name of the calling app
|
|
||||||
* @param type the type of the in-app items being requested
|
|
||||||
* ("inapp" for one-time purchases and "subs" for subscription).
|
|
||||||
* @param continuationToken to be set as null for the first call, if the number of owned
|
|
||||||
* skus are too many, a continuationToken is returned in the response bundle.
|
|
||||||
* This method can be called again with the continuation token to get the next set of
|
|
||||||
* owned skus.
|
|
||||||
* @return Bundle containing the following key-value pairs
|
|
||||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
|
|
||||||
* failure as listed above.
|
|
||||||
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
|
|
||||||
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
|
|
||||||
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
|
|
||||||
* of the purchase information
|
|
||||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
|
|
||||||
* next set of in-app purchases. Only set if the
|
|
||||||
* user has more owned skus than the current list.
|
|
||||||
*/
|
|
||||||
Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Consume the last purchase of the given SKU. This will result in this item being removed
|
|
||||||
* from all subsequent responses to getPurchases() and allow re-purchase of this item.
|
|
||||||
* @param apiVersion billing API version that the app is using
|
|
||||||
* @param packageName package name of the calling app
|
|
||||||
* @param purchaseToken token in the purchase information JSON that identifies the purchase
|
|
||||||
* to be consumed
|
|
||||||
* @return 0 if consumption succeeded. Appropriate error values for failures.
|
|
||||||
*/
|
|
||||||
int consumePurchase(int apiVersion, String packageName, String purchaseToken);
|
|
||||||
}
|
|
||||||
1
V2rayNG/app/src/main/assets/custom_routing_block
Normal file
@@ -0,0 +1 @@
|
|||||||
|
geosite:category-ads-all,
|
||||||
132
V2rayNG/app/src/main/assets/custom_routing_direct
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
domain:12306.com,
|
||||||
|
domain:51ym.me,
|
||||||
|
domain:52pojie.cn,
|
||||||
|
domain:8686c.com,
|
||||||
|
domain:abercrombie.com,
|
||||||
|
domain:adobesc.com,
|
||||||
|
domain:air-matters.com,
|
||||||
|
domain:air-matters.io,
|
||||||
|
domain:airtable.com,
|
||||||
|
domain:akadns.net,
|
||||||
|
domain:apache.org,
|
||||||
|
domain:api.crisp.chat,
|
||||||
|
domain:api.termius.com,
|
||||||
|
domain:appshike.com,
|
||||||
|
domain:appstore.com,
|
||||||
|
domain:aweme.snssdk.com,
|
||||||
|
domain:bababian.com,
|
||||||
|
domain:battle.net,
|
||||||
|
domain:beatsbydre.com,
|
||||||
|
domain:bet365.com,
|
||||||
|
domain:bilibili.cn,
|
||||||
|
domain:ccgslb.com,
|
||||||
|
domain:ccgslb.net,
|
||||||
|
domain:chunbo.com,
|
||||||
|
domain:chunboimg.com,
|
||||||
|
domain:clashroyaleapp.com,
|
||||||
|
domain:cloudsigma.com,
|
||||||
|
domain:cloudxns.net,
|
||||||
|
domain:cmfu.com,
|
||||||
|
domain:culturedcode.com,
|
||||||
|
domain:dct-cloud.com,
|
||||||
|
domain:didialift.com,
|
||||||
|
domain:douyutv.com,
|
||||||
|
domain:duokan.com,
|
||||||
|
domain:dytt8.net,
|
||||||
|
domain:easou.com,
|
||||||
|
domain:ecitic.net,
|
||||||
|
domain:eclipse.org,
|
||||||
|
domain:eudic.net,
|
||||||
|
domain:ewqcxz.com,
|
||||||
|
domain:fir.im,
|
||||||
|
domain:frdic.com,
|
||||||
|
domain:fresh-ideas.cc,
|
||||||
|
domain:godic.net,
|
||||||
|
domain:goodread.com,
|
||||||
|
domain:haibian.com,
|
||||||
|
domain:hdslb.net,
|
||||||
|
domain:hollisterco.com,
|
||||||
|
domain:hongxiu.com,
|
||||||
|
domain:hxcdn.net,
|
||||||
|
domain:images.unsplash.com,
|
||||||
|
domain:img4me.com,
|
||||||
|
domain:ipify.org,
|
||||||
|
domain:ixdzs.com,
|
||||||
|
domain:jd.hk,
|
||||||
|
domain:jianshuapi.com,
|
||||||
|
domain:jomodns.com,
|
||||||
|
domain:jsboxbbs.com,
|
||||||
|
domain:knewone.com,
|
||||||
|
domain:kuaidi100.com,
|
||||||
|
domain:lemicp.com,
|
||||||
|
domain:letvcloud.com,
|
||||||
|
domain:lizhi.io,
|
||||||
|
domain:localizecdn.com,
|
||||||
|
domain:lucifr.com,
|
||||||
|
domain:luoo.net,
|
||||||
|
domain:mai.tn,
|
||||||
|
domain:maven.org,
|
||||||
|
domain:miwifi.com,
|
||||||
|
domain:moji.com,
|
||||||
|
domain:moke.com,
|
||||||
|
domain:mtalk.google.com,
|
||||||
|
domain:mxhichina.com,
|
||||||
|
domain:myqcloud.com,
|
||||||
|
domain:myunlu.com,
|
||||||
|
domain:netease.com,
|
||||||
|
domain:nfoservers.com,
|
||||||
|
domain:nssurge.com,
|
||||||
|
domain:nuomi.com,
|
||||||
|
domain:ourdvs.com,
|
||||||
|
domain:overcast.fm,
|
||||||
|
domain:paypal.com,
|
||||||
|
domain:paypalobjects.com,
|
||||||
|
domain:pgyer.com,
|
||||||
|
domain:qdaily.com,
|
||||||
|
domain:qdmm.com,
|
||||||
|
domain:qin.io,
|
||||||
|
domain:qingmang.me,
|
||||||
|
domain:qingmang.mobi,
|
||||||
|
domain:qqurl.com,
|
||||||
|
domain:rarbg.to,
|
||||||
|
domain:rrmj.tv,
|
||||||
|
domain:ruguoapp.com,
|
||||||
|
domain:sm.ms,
|
||||||
|
domain:snwx.com,
|
||||||
|
domain:soku.com,
|
||||||
|
domain:startssl.com,
|
||||||
|
domain:store.steampowered.com,
|
||||||
|
domain:symcd.com,
|
||||||
|
domain:teamviewer.com,
|
||||||
|
domain:tmzvps.com,
|
||||||
|
domain:trello.com,
|
||||||
|
domain:trellocdn.com,
|
||||||
|
domain:ttmeiju.com,
|
||||||
|
domain:udache.com,
|
||||||
|
domain:uxengine.net,
|
||||||
|
domain:weather.bjango.com,
|
||||||
|
domain:weather.com,
|
||||||
|
domain:webqxs.com,
|
||||||
|
domain:weico.cc,
|
||||||
|
domain:wenku8.net,
|
||||||
|
domain:werewolf.53site.com,
|
||||||
|
domain:windowsupdate.com,
|
||||||
|
domain:wkcdn.com,
|
||||||
|
domain:workflowy.com,
|
||||||
|
domain:xdrig.com,
|
||||||
|
domain:xiaojukeji.com,
|
||||||
|
domain:xiaomi.net,
|
||||||
|
domain:xiaomicp.com,
|
||||||
|
domain:ximalaya.com,
|
||||||
|
domain:xitek.com,
|
||||||
|
domain:xmcdn.com,
|
||||||
|
domain:xslb.net,
|
||||||
|
domain:xteko.com,
|
||||||
|
domain:yach.me,
|
||||||
|
domain:yixia.com,
|
||||||
|
domain:yunjiasu-cdn.net,
|
||||||
|
domain:zealer.com,
|
||||||
|
domain:zgslb.net,
|
||||||
|
domain:zimuzu.tv,
|
||||||
|
domain:zmz002.com,
|
||||||
|
domain:samsungdm.com,
|
||||||
33
V2rayNG/app/src/main/assets/custom_routing_proxy
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
geosite:google,
|
||||||
|
geosite:github,
|
||||||
|
geosite:netflix,
|
||||||
|
geosite:steam,
|
||||||
|
geosite:telegram,
|
||||||
|
geosite:tumblr,
|
||||||
|
geosite:speedtest,
|
||||||
|
geosite:bbc,
|
||||||
|
domain:gvt1.com,
|
||||||
|
domain:textnow.com,
|
||||||
|
domain:twitch.tv,
|
||||||
|
domain:wikileaks.org,
|
||||||
|
domain:naver.com,
|
||||||
|
91.108.4.0/22,
|
||||||
|
91.108.8.0/22,
|
||||||
|
91.108.12.0/22,
|
||||||
|
91.108.20.0/22,
|
||||||
|
91.108.36.0/23,
|
||||||
|
91.108.38.0/23,
|
||||||
|
91.108.56.0/22,
|
||||||
|
149.154.160.0/20,
|
||||||
|
149.154.164.0/22,
|
||||||
|
149.154.172.0/22,
|
||||||
|
74.125.0.0/16,
|
||||||
|
173.194.0.0/16,
|
||||||
|
172.217.0.0/16,
|
||||||
|
216.58.200.0/24,
|
||||||
|
216.58.220.0/24,
|
||||||
|
91.108.56.116,
|
||||||
|
91.108.56.0/24,
|
||||||
|
109.239.140.0/24,
|
||||||
|
149.154.167.0/24,
|
||||||
|
149.154.175.0/24,
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
|
"id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
|
||||||
"alterId": 64,
|
"alterId": 0,
|
||||||
"security": "auto",
|
"security": "auto",
|
||||||
"level": 8
|
"level": 8
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 12 KiB |
@@ -1,116 +0,0 @@
|
|||||||
package com.v2ray.ang.util;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
|
||||||
import com.google.zxing.BinaryBitmap;
|
|
||||||
import com.google.zxing.DecodeHintType;
|
|
||||||
import com.google.zxing.MultiFormatReader;
|
|
||||||
import com.google.zxing.RGBLuminanceSource;
|
|
||||||
import com.google.zxing.Result;
|
|
||||||
import com.google.zxing.common.GlobalHistogramBinarizer;
|
|
||||||
import com.google.zxing.common.HybridBinarizer;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 描述:解析二维码图片
|
|
||||||
*/
|
|
||||||
public class QRCodeDecoder {
|
|
||||||
public static final Map<DecodeHintType, Object> HINTS = new EnumMap<>(DecodeHintType.class);
|
|
||||||
|
|
||||||
static {
|
|
||||||
List<BarcodeFormat> allFormats = new ArrayList<>();
|
|
||||||
allFormats.add(BarcodeFormat.AZTEC);
|
|
||||||
allFormats.add(BarcodeFormat.CODABAR);
|
|
||||||
allFormats.add(BarcodeFormat.CODE_39);
|
|
||||||
allFormats.add(BarcodeFormat.CODE_93);
|
|
||||||
allFormats.add(BarcodeFormat.CODE_128);
|
|
||||||
allFormats.add(BarcodeFormat.DATA_MATRIX);
|
|
||||||
allFormats.add(BarcodeFormat.EAN_8);
|
|
||||||
allFormats.add(BarcodeFormat.EAN_13);
|
|
||||||
allFormats.add(BarcodeFormat.ITF);
|
|
||||||
allFormats.add(BarcodeFormat.MAXICODE);
|
|
||||||
allFormats.add(BarcodeFormat.PDF_417);
|
|
||||||
allFormats.add(BarcodeFormat.QR_CODE);
|
|
||||||
allFormats.add(BarcodeFormat.RSS_14);
|
|
||||||
allFormats.add(BarcodeFormat.RSS_EXPANDED);
|
|
||||||
allFormats.add(BarcodeFormat.UPC_A);
|
|
||||||
allFormats.add(BarcodeFormat.UPC_E);
|
|
||||||
allFormats.add(BarcodeFormat.UPC_EAN_EXTENSION);
|
|
||||||
HINTS.put(DecodeHintType.TRY_HARDER, BarcodeFormat.QR_CODE);
|
|
||||||
HINTS.put(DecodeHintType.POSSIBLE_FORMATS, allFormats);
|
|
||||||
HINTS.put(DecodeHintType.CHARACTER_SET, "utf-8");
|
|
||||||
}
|
|
||||||
|
|
||||||
private QRCodeDecoder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
|
|
||||||
*
|
|
||||||
* @param picturePath 要解析的二维码图片本地路径
|
|
||||||
* @return 返回二维码图片里的内容 或 null
|
|
||||||
*/
|
|
||||||
public static String syncDecodeQRCode(String picturePath) {
|
|
||||||
return syncDecodeQRCode(getDecodeAbleBitmap(picturePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。
|
|
||||||
*
|
|
||||||
* @param bitmap 要解析的二维码图片
|
|
||||||
* @return 返回二维码图片里的内容 或 null
|
|
||||||
*/
|
|
||||||
public static String syncDecodeQRCode(Bitmap bitmap) {
|
|
||||||
Result result = null;
|
|
||||||
RGBLuminanceSource source = null;
|
|
||||||
try {
|
|
||||||
int width = bitmap.getWidth();
|
|
||||||
int height = bitmap.getHeight();
|
|
||||||
int[] pixels = new int[width * height];
|
|
||||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
|
|
||||||
source = new RGBLuminanceSource(width, height, pixels);
|
|
||||||
result = new MultiFormatReader().decode(new BinaryBitmap(new HybridBinarizer(source)), HINTS);
|
|
||||||
return result.getText();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
if (source != null) {
|
|
||||||
try {
|
|
||||||
result = new MultiFormatReader().decode(new BinaryBitmap(new GlobalHistogramBinarizer(source)), HINTS);
|
|
||||||
return result.getText();
|
|
||||||
} catch (Throwable e2) {
|
|
||||||
e2.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。感谢 https://github.com/devilsen 提的 PR
|
|
||||||
*
|
|
||||||
* @param picturePath 本地图片文件路径
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static Bitmap getDecodeAbleBitmap(String picturePath) {
|
|
||||||
try {
|
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
|
||||||
options.inJustDecodeBounds = true;
|
|
||||||
BitmapFactory.decodeFile(picturePath, options);
|
|
||||||
int sampleSize = options.outHeight / 400;
|
|
||||||
if (sampleSize <= 0) {
|
|
||||||
sampleSize = 1;
|
|
||||||
}
|
|
||||||
options.inSampleSize = sampleSize;
|
|
||||||
options.inJustDecodeBounds = false;
|
|
||||||
return BitmapFactory.decodeFile(picturePath, options);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,6 @@ class AngApplication : MultiDexApplication() {
|
|||||||
const val PREF_LAST_VERSION = "pref_last_version"
|
const val PREF_LAST_VERSION = "pref_last_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
var curIndex = -1 //Current proxy that is opened. (Used to implement restart feature)
|
|
||||||
var firstRun = false
|
var firstRun = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package com.v2ray.ang
|
|||||||
*/
|
*/
|
||||||
object AppConfig {
|
object AppConfig {
|
||||||
const val ANG_PACKAGE = "com.v2ray.ang"
|
const val ANG_PACKAGE = "com.v2ray.ang"
|
||||||
|
const val DIR_ASSETS = "assets"
|
||||||
|
|
||||||
// legacy
|
// legacy
|
||||||
const val ANG_CONFIG = "ang_config"
|
const val ANG_CONFIG = "ang_config"
|
||||||
@@ -23,7 +24,12 @@ object AppConfig {
|
|||||||
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
const val PREF_REMOTE_DNS = "pref_remote_dns"
|
||||||
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
|
||||||
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
|
||||||
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
const val PREF_ALLOW_INSECURE = "pref_allow_insecure"
|
||||||
|
const val PREF_SOCKS_PORT = "pref_socks_port"
|
||||||
|
const val PREF_HTTP_PORT = "pref_http_port"
|
||||||
|
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||||
|
const val PREF_LANGUAGE = "pref_language"
|
||||||
|
const val PREF_PREFER_IPV6 = "pref_prefer_ipv6"
|
||||||
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
|
||||||
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
const val PREF_ROUTING_MODE = "pref_routing_mode"
|
||||||
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
|
||||||
@@ -32,16 +38,7 @@ object AppConfig {
|
|||||||
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
|
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_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
|
||||||
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
const val PREF_BYPASS_APPS = "pref_bypass_apps"
|
||||||
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
|
const val PREF_CONFIRM_REMOVE = "pref_confirm_remove"
|
||||||
// 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 HTTP_PROTOCOL: String = "http://"
|
||||||
const val HTTPS_PROTOCOL: String = "https://"
|
const val HTTPS_PROTOCOL: String = "https://"
|
||||||
@@ -64,11 +61,16 @@ object AppConfig {
|
|||||||
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
||||||
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
|
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
|
||||||
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
|
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
|
||||||
const val promotionUrl = "https://1.2345345.xyz/ads.html"
|
const val promotionUrl = "aHR0cHM6Ly85LjIzNDQ1Ni54eXovYWJjLmh0bWw="
|
||||||
|
const val geoUrl = "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/";
|
||||||
|
|
||||||
const val DNS_AGENT = "1.1.1.1"
|
const val DNS_AGENT = "1.1.1.1"
|
||||||
const val DNS_DIRECT = "223.5.5.5"
|
const val DNS_DIRECT = "223.5.5.5"
|
||||||
|
|
||||||
|
const val PORT_LOCAL_DNS = "10853"
|
||||||
|
const val PORT_SOCKS = "10808"
|
||||||
|
const val PORT_HTTP = "10809"
|
||||||
|
|
||||||
const val MSG_REGISTER_CLIENT = 1
|
const val MSG_REGISTER_CLIENT = 1
|
||||||
const val MSG_STATE_RUNNING = 11
|
const val MSG_STATE_RUNNING = 11
|
||||||
const val MSG_STATE_NOT_RUNNING = 12
|
const val MSG_STATE_NOT_RUNNING = 12
|
||||||
@@ -79,4 +81,9 @@ object AppConfig {
|
|||||||
const val MSG_STATE_STOP = 4
|
const val MSG_STATE_STOP = 4
|
||||||
const val MSG_STATE_STOP_SUCCESS = 41
|
const val MSG_STATE_STOP_SUCCESS = 41
|
||||||
const val MSG_STATE_RESTART = 5
|
const val MSG_STATE_RESTART = 5
|
||||||
|
const val MSG_MEASURE_DELAY = 6
|
||||||
|
const val MSG_MEASURE_DELAY_SUCCESS = 61
|
||||||
|
const val MSG_MEASURE_CONFIG = 7
|
||||||
|
const val MSG_MEASURE_CONFIG_SUCCESS = 71
|
||||||
|
const val MSG_MEASURE_CONFIG_CANCEL = 72
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,12 +17,16 @@ data class AngConfig(
|
|||||||
var requestHost: String = "",
|
var requestHost: String = "",
|
||||||
var path: String = "",
|
var path: String = "",
|
||||||
var streamSecurity: String = "",
|
var streamSecurity: String = "",
|
||||||
|
var allowInsecure: String = "",
|
||||||
var configType: Int = 1,
|
var configType: Int = 1,
|
||||||
var configVersion: Int = 1,
|
var configVersion: Int = 1,
|
||||||
var testResult: String = "",
|
var testResult: String = "",
|
||||||
var subid: String = "")
|
var subid: String = "",
|
||||||
|
var flow: String = "",
|
||||||
|
var sni: String = "")
|
||||||
|
|
||||||
data class SubItemBean(var id: String = "",
|
data class SubItemBean(var id: String = "",
|
||||||
var remarks: String = "",
|
var remarks: String = "",
|
||||||
var url: String = "")
|
var url: String = "",
|
||||||
|
var enabled: Boolean = true)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
enum class ERoutingMode(val value: String ) {
|
||||||
|
GLOBAL_PROXY("0"),
|
||||||
|
BYPASS_LAN("1"),
|
||||||
|
BYPASS_MAINLAND("2"),
|
||||||
|
BYPASS_LAN_MAINLAND("3"),
|
||||||
|
GLOBAL_DIRECT("4");
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.v2ray.ang.dto
|
||||||
|
|
||||||
|
data class ServersCache(val guid: String,
|
||||||
|
val config: ServerConfig)
|
||||||
@@ -20,7 +20,7 @@ data class V2rayConfig(
|
|||||||
val api: Any? = null,
|
val api: Any? = null,
|
||||||
val transport: Any? = null,
|
val transport: Any? = null,
|
||||||
val reverse: Any? = null,
|
val reverse: Any? = null,
|
||||||
var fakedns: FakednsBean? = null,
|
var fakedns: Any? = null,
|
||||||
val browserForwarder: Any? = null) {
|
val browserForwarder: Any? = null) {
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_PORT = 443
|
const val DEFAULT_PORT = 443
|
||||||
@@ -103,7 +103,7 @@ data class V2rayConfig(
|
|||||||
var port: Int = DEFAULT_PORT,
|
var port: Int = DEFAULT_PORT,
|
||||||
var level: Int = DEFAULT_LEVEL,
|
var level: Int = DEFAULT_LEVEL,
|
||||||
val email: String? = null,
|
val email: String? = null,
|
||||||
val flow: String? = null,
|
var flow: String? = null,
|
||||||
val ivCheck: Boolean? = null,
|
val ivCheck: Boolean? = null,
|
||||||
var users: List<SocksUsersBean>? = null) {
|
var users: List<SocksUsersBean>? = null) {
|
||||||
|
|
||||||
@@ -267,7 +267,9 @@ data class V2rayConfig(
|
|||||||
)
|
)
|
||||||
if (security == TLS) {
|
if (security == TLS) {
|
||||||
tlsSettings = tlsSetting
|
tlsSettings = tlsSetting
|
||||||
|
xtlsSettings = null
|
||||||
} else if (security == XTLS) {
|
} else if (security == XTLS) {
|
||||||
|
tlsSettings = null
|
||||||
xtlsSettings = tlsSetting
|
xtlsSettings = tlsSetting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,7 +325,8 @@ data class V2rayConfig(
|
|||||||
|
|
||||||
fun getTransportSettingDetails(): List<String>? {
|
fun getTransportSettingDetails(): List<String>? {
|
||||||
if (protocol.equals(EConfigType.VMESS.name, true)
|
if (protocol.equals(EConfigType.VMESS.name, true)
|
||||||
|| protocol.equals(EConfigType.VLESS.name, true)) {
|
|| protocol.equals(EConfigType.VLESS.name, true)
|
||||||
|
|| protocol.equals(EConfigType.TROJAN.name, true)) {
|
||||||
val transport = streamSettings?.network ?: return null
|
val transport = streamSettings?.network ?: return null
|
||||||
return when (transport) {
|
return when (transport) {
|
||||||
"tcp" -> {
|
"tcp" -> {
|
||||||
@@ -370,7 +373,7 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class DnsBean(var servers: ArrayList<Any>? = null,
|
data class DnsBean(var servers: ArrayList<Any>? = null,
|
||||||
var hosts: Map<String, String>? = null,
|
var hosts: Map<String, Any>? = null,
|
||||||
val clientIp: String? = null,
|
val clientIp: String? = null,
|
||||||
val disableCache: Boolean? = null,
|
val disableCache: Boolean? = null,
|
||||||
val queryStrategy: String? = null,
|
val queryStrategy: String? = null,
|
||||||
@@ -384,7 +387,7 @@ data class V2rayConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class RoutingBean(var domainStrategy: String,
|
data class RoutingBean(var domainStrategy: String,
|
||||||
val domainMatcher: String? = null,
|
var domainMatcher: String? = null,
|
||||||
var rules: ArrayList<RulesBean>,
|
var rules: ArrayList<RulesBean>,
|
||||||
val balancers: List<Any>? = null) {
|
val balancers: List<Any>? = null) {
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ data class VmessQRCode(var v: String = "",
|
|||||||
var add: String = "",
|
var add: String = "",
|
||||||
var port: String = "",
|
var port: String = "",
|
||||||
var id: String = "",
|
var id: String = "",
|
||||||
var aid: String = "",
|
var aid: String = "0",
|
||||||
|
var scy: String = "",
|
||||||
var net: String = "",
|
var net: String = "",
|
||||||
var type: String = "",
|
var type: String = "",
|
||||||
var host: String = "",
|
var host: String = "",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.widget.Toast
|
|||||||
import com.v2ray.ang.AngApplication
|
import com.v2ray.ang.AngApplication
|
||||||
import me.drakeet.support.toast.ToastCompat
|
import me.drakeet.support.toast.ToastCompat
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.net.URI
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,13 +16,13 @@ import java.net.URLConnection
|
|||||||
val Context.v2RayApplication: AngApplication
|
val Context.v2RayApplication: AngApplication
|
||||||
get() = applicationContext as AngApplication
|
get() = applicationContext as AngApplication
|
||||||
|
|
||||||
inline fun Context.toast(message: Int): Toast = ToastCompat
|
fun Context.toast(message: Int): Toast = ToastCompat
|
||||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||||
.apply {
|
.apply {
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun Context.toast(message: CharSequence): Toast = ToastCompat
|
fun Context.toast(message: CharSequence): Toast = ToastCompat
|
||||||
.makeText(this, message, Toast.LENGTH_SHORT)
|
.makeText(this, message, Toast.LENGTH_SHORT)
|
||||||
.apply {
|
.apply {
|
||||||
show()
|
show()
|
||||||
@@ -73,4 +74,7 @@ private fun Float.toShortString(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val URLConnection.responseLength: Long
|
val URLConnection.responseLength: Long
|
||||||
get() = if (Build.VERSION.SDK_INT >= 24) contentLengthLong else contentLength.toLong()
|
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) contentLengthLong else contentLength.toLong()
|
||||||
|
|
||||||
|
val URI.idnHost: String
|
||||||
|
get() = (host!!).replace("[", "").replace("]", "")
|
||||||
@@ -6,6 +6,7 @@ import android.appwidget.AppWidgetProvider
|
|||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
@@ -21,16 +22,33 @@ class WidgetProvider : AppWidgetProvider() {
|
|||||||
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
|
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
|
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
|
||||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
|
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
|
||||||
val intent = Intent(context, WidgetProvider::class.java)
|
val intent = Intent(context, WidgetProvider::class.java)
|
||||||
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
|
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)
|
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||||
if (isRunning) {
|
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 {
|
} 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) {
|
for (appWidgetId in appWidgetIds) {
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ class QSTileService : TileService() {
|
|||||||
if (state == Tile.STATE_INACTIVE) {
|
if (state == Tile.STATE_INACTIVE) {
|
||||||
qsTile?.state = Tile.STATE_INACTIVE
|
qsTile?.state = Tile.STATE_INACTIVE
|
||||||
qsTile?.label = getString(R.string.app_name)
|
qsTile?.label = getString(R.string.app_name)
|
||||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
|
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
||||||
} else if (state == Tile.STATE_ACTIVE) {
|
} else if (state == Tile.STATE_ACTIVE) {
|
||||||
qsTile?.state = Tile.STATE_ACTIVE
|
qsTile?.state = Tile.STATE_ACTIVE
|
||||||
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
|
qsTile?.label = V2RayServiceManager.currentConfig?.remarks
|
||||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
|
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_stat_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
qsTile?.updateTile()
|
qsTile?.updateTile()
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ import android.app.Service
|
|||||||
interface ServiceControl {
|
interface ServiceControl {
|
||||||
fun getService(): Service
|
fun getService(): Service
|
||||||
|
|
||||||
fun startService(parameters: String)
|
fun startService()
|
||||||
|
|
||||||
fun stopService()
|
fun stopService()
|
||||||
|
|
||||||
fun vpnProtect(socket: Int): Boolean
|
fun vpnProtect(socket: Int): Boolean
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
package com.v2ray.ang.service
|
package com.v2ray.ang.service
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.v2ray.ang.util.MyContextWrapper
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
|
|
||||||
class V2RayProxyOnlyService : Service(), ServiceControl {
|
class V2RayProxyOnlyService : Service(), ServiceControl {
|
||||||
@@ -25,7 +30,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startService(parameters: String) {
|
override fun startService() {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,4 +45,12 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
|
|||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
|
val context = newBase?.let {
|
||||||
|
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
||||||
|
}
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
package com.v2ray.ang.service
|
package com.v2ray.ang.service
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.*
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import android.util.Log
|
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
@@ -44,7 +41,7 @@ object V2RayServiceManager {
|
|||||||
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
|
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
|
||||||
private const val NOTIFICATION_ICON_THRESHOLD = 3000
|
private const val NOTIFICATION_ICON_THRESHOLD = 3000
|
||||||
|
|
||||||
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
|
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback(), Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
|
||||||
private val mMsgReceive = ReceiveMessageHandler()
|
private val mMsgReceive = ReceiveMessageHandler()
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
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 settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
@@ -52,12 +49,8 @@ object V2RayServiceManager {
|
|||||||
var serviceControl: SoftReference<ServiceControl>? = null
|
var serviceControl: SoftReference<ServiceControl>? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
val context = value?.get()?.getService()?.applicationContext
|
Seq.setContext(value?.get()?.getService()?.applicationContext)
|
||||||
context?.let {
|
Libv2ray.initV2Env(Utils.userAssetPath(value?.get()?.getService()))
|
||||||
v2rayPoint.packageName = Utils.packagePath(context)
|
|
||||||
v2rayPoint.packageCodePath = context.applicationInfo.nativeLibraryDir + "/"
|
|
||||||
Seq.setContext(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var currentConfig: ServerConfig? = null
|
var currentConfig: ServerConfig? = null
|
||||||
|
|
||||||
@@ -88,7 +81,6 @@ object V2RayServiceManager {
|
|||||||
override fun shutdown(): Long {
|
override fun shutdown(): Long {
|
||||||
val serviceControl = serviceControl?.get() ?: return -1
|
val serviceControl = serviceControl?.get() ?: return -1
|
||||||
// called by go
|
// called by go
|
||||||
// shutdown the whole vpn service
|
|
||||||
return try {
|
return try {
|
||||||
serviceControl.stopService()
|
serviceControl.stopService()
|
||||||
0
|
0
|
||||||
@@ -102,9 +94,9 @@ object V2RayServiceManager {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun protect(l: Long): Long {
|
override fun protect(l: Long): Boolean {
|
||||||
val serviceControl = serviceControl?.get() ?: return 0
|
val serviceControl = serviceControl?.get() ?: return true
|
||||||
return if (serviceControl.vpnProtect(l.toInt())) 0 else 1
|
return serviceControl.vpnProtect(l.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEmitStatus(l: Long, s: String?): Long {
|
override fun onEmitStatus(l: Long, s: String?): Long {
|
||||||
@@ -116,7 +108,7 @@ object V2RayServiceManager {
|
|||||||
val serviceControl = serviceControl?.get() ?: return -1
|
val serviceControl = serviceControl?.get() ?: return -1
|
||||||
//Logger.d(s)
|
//Logger.d(s)
|
||||||
return try {
|
return try {
|
||||||
serviceControl.startService(s)
|
serviceControl.startService()
|
||||||
lastQueryTime = System.currentTimeMillis()
|
lastQueryTime = System.currentTimeMillis()
|
||||||
startSpeedNotification()
|
startSpeedNotification()
|
||||||
0
|
0
|
||||||
@@ -125,7 +117,6 @@ object V2RayServiceManager {
|
|||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startV2rayPoint() {
|
fun startV2rayPoint() {
|
||||||
@@ -150,12 +141,9 @@ object V2RayServiceManager {
|
|||||||
v2rayPoint.configureFileContent = result.content
|
v2rayPoint.configureFileContent = result.content
|
||||||
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
|
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
|
||||||
currentConfig = config
|
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 {
|
try {
|
||||||
v2rayPoint.runLoop()
|
v2rayPoint.runLoop(settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) ?: false)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d(ANG_PACKAGE, e.toString())
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
@@ -217,6 +205,9 @@ object V2RayServiceManager {
|
|||||||
AppConfig.MSG_STATE_RESTART -> {
|
AppConfig.MSG_STATE_RESTART -> {
|
||||||
startV2rayPoint()
|
startV2rayPoint()
|
||||||
}
|
}
|
||||||
|
AppConfig.MSG_MEASURE_DELAY -> {
|
||||||
|
measureV2rayDelay()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
@@ -232,12 +223,39 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun measureV2rayDelay() {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
val service = serviceControl?.get()?.getService() ?: return@launch
|
||||||
|
var time = -1L
|
||||||
|
var errstr = ""
|
||||||
|
if (v2rayPoint.isRunning) {
|
||||||
|
try {
|
||||||
|
time = v2rayPoint.measureDelay()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(ANG_PACKAGE, "measureV2rayDelay: $e")
|
||||||
|
errstr = e.message?.substringAfter("\":") ?: "empty message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val result = if (time == -1L) {
|
||||||
|
service.getString(R.string.connection_test_error, errstr)
|
||||||
|
} else {
|
||||||
|
service.getString(R.string.connection_test_available, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageUtil.sendMsg2UI(service, AppConfig.MSG_MEASURE_DELAY_SUCCESS, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showNotification() {
|
private fun showNotification() {
|
||||||
val service = serviceControl?.get()?.getService() ?: return
|
val service = serviceControl?.get()?.getService() ?: return
|
||||||
val startMainIntent = Intent(service, MainActivity::class.java)
|
val startMainIntent = Intent(service, MainActivity::class.java)
|
||||||
val contentPendingIntent = PendingIntent.getActivity(service,
|
val contentPendingIntent = PendingIntent.getActivity(service,
|
||||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
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)
|
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||||
stopV2RayIntent.`package` = ANG_PACKAGE
|
stopV2RayIntent.`package` = ANG_PACKAGE
|
||||||
@@ -245,7 +263,11 @@ object V2RayServiceManager {
|
|||||||
|
|
||||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
|
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
|
||||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
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 =
|
val channelId =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
@@ -257,7 +279,7 @@ object V2RayServiceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mBuilder = NotificationCompat.Builder(service, channelId)
|
mBuilder = NotificationCompat.Builder(service, channelId)
|
||||||
.setSmallIcon(R.drawable.ic_v)
|
.setSmallIcon(R.drawable.ic_stat_name)
|
||||||
.setContentTitle(currentConfig?.remarks)
|
.setContentTitle(currentConfig?.remarks)
|
||||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
@@ -298,7 +320,7 @@ object V2RayServiceManager {
|
|||||||
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
private fun updateNotification(contentText: String?, proxyTraffic: Long, directTraffic: Long) {
|
||||||
if (mBuilder != null) {
|
if (mBuilder != null) {
|
||||||
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
|
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
|
||||||
mBuilder?.setSmallIcon(R.drawable.ic_v)
|
mBuilder?.setSmallIcon(R.drawable.ic_stat_name)
|
||||||
} else if (proxyTraffic > directTraffic) {
|
} else if (proxyTraffic > directTraffic) {
|
||||||
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
|
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
|
||||||
} else {
|
} else {
|
||||||
@@ -318,7 +340,7 @@ object V2RayServiceManager {
|
|||||||
return mNotificationManager
|
return mNotificationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startSpeedNotification() {
|
private fun startSpeedNotification() {
|
||||||
if (mSubscription == null &&
|
if (mSubscription == null &&
|
||||||
v2rayPoint.isRunning &&
|
v2rayPoint.isRunning &&
|
||||||
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
|
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
|
||||||
@@ -367,7 +389,7 @@ object V2RayServiceManager {
|
|||||||
text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
|
text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopSpeedNotification() {
|
private fun stopSpeedNotification() {
|
||||||
if (mSubscription != null) {
|
if (mSubscription != null) {
|
||||||
mSubscription?.unsubscribe() //stop queryStats
|
mSubscription?.unsubscribe() //stop queryStats
|
||||||
mSubscription = null
|
mSubscription = null
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.v2ray.ang.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.IBinder
|
||||||
|
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG
|
||||||
|
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_CANCEL
|
||||||
|
import com.v2ray.ang.AppConfig.MSG_MEASURE_CONFIG_SUCCESS
|
||||||
|
import com.v2ray.ang.util.MessageUtil
|
||||||
|
import com.v2ray.ang.util.SpeedtestUtil
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import go.Seq
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import libv2ray.Libv2ray
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class V2RayTestService : Service() {
|
||||||
|
private val realTestScope by lazy { CoroutineScope(Executors.newFixedThreadPool(10).asCoroutineDispatcher()) }
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
Seq.setContext(this)
|
||||||
|
Libv2ray.initV2Env(Utils.userAssetPath(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
when (intent?.getIntExtra("key", 0)) {
|
||||||
|
MSG_MEASURE_CONFIG -> {
|
||||||
|
val contentPair = intent.getSerializableExtra("content") as Pair<String, String>
|
||||||
|
realTestScope.launch {
|
||||||
|
val result = SpeedtestUtil.realPing(contentPair.second)
|
||||||
|
MessageUtil.sendMsg2UI(this@V2RayTestService, MSG_MEASURE_CONFIG_SUCCESS, Pair(contentPair.first, result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MSG_MEASURE_CONFIG_CANCEL -> {
|
||||||
|
realTestScope.coroutineContext[Job]?.cancelChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.v2ray.ang.service
|
package com.v2ray.ang.service
|
||||||
|
|
||||||
import android.app.*
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@@ -8,12 +8,14 @@ import android.net.*
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.dto.ERoutingMode
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.MyContextWrapper
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
@@ -22,11 +24,23 @@ import java.io.File
|
|||||||
import java.lang.ref.SoftReference
|
import java.lang.ref.SoftReference
|
||||||
|
|
||||||
class V2RayVpnService : VpnService(), ServiceControl {
|
class V2RayVpnService : VpnService(), ServiceControl {
|
||||||
|
companion object {
|
||||||
|
private const val VPN_MTU = 1500
|
||||||
|
private const val PRIVATE_VLAN4_CLIENT = "26.26.26.1"
|
||||||
|
private const val PRIVATE_VLAN4_ROUTER = "26.26.26.2"
|
||||||
|
private const val PRIVATE_VLAN6_CLIENT = "da26:2626::1"
|
||||||
|
private const val PRIVATE_VLAN6_ROUTER = "da26:2626::2"
|
||||||
|
private const val TUN2SOCKS = "libtun2socks.so"
|
||||||
|
}
|
||||||
|
|
||||||
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
private lateinit var mInterface: ParcelFileDescriptor
|
private lateinit var mInterface: ParcelFileDescriptor
|
||||||
|
|
||||||
/**
|
//val fd: Int get() = mInterface.fd
|
||||||
|
private lateinit var process: Process
|
||||||
|
|
||||||
|
/**destroy
|
||||||
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
|
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
|
||||||
*
|
*
|
||||||
* This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that
|
* This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that
|
||||||
@@ -51,10 +65,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
setUnderlyingNetworks(arrayOf(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
|
// it's a good idea to refresh capabilities
|
||||||
setUnderlyingNetworks(arrayOf(network))
|
setUnderlyingNetworks(arrayOf(network))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
override fun onLost(network: Network) {
|
||||||
setUnderlyingNetworks(null)
|
setUnderlyingNetworks(null)
|
||||||
}
|
}
|
||||||
@@ -73,18 +89,17 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
stopV2Ray()
|
stopV2Ray()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLowMemory() {
|
// override fun onLowMemory() {
|
||||||
stopV2Ray()
|
// stopV2Ray()
|
||||||
super.onLowMemory()
|
// super.onLowMemory()
|
||||||
}
|
// }
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
stopV2Ray()
|
V2RayServiceManager.cancelNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setup(parameters: String) {
|
private fun setup() {
|
||||||
|
|
||||||
val prepare = prepare(this)
|
val prepare = prepare(this)
|
||||||
if (prepare != null) {
|
if (prepare != null) {
|
||||||
return
|
return
|
||||||
@@ -93,35 +108,34 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
// If the old interface has exactly the same parameters, use it!
|
// If the old interface has exactly the same parameters, use it!
|
||||||
// Configure a builder while parsing the parameters.
|
// Configure a builder while parsing the parameters.
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
|
//val enableLocalDns = defaultDPreference.getPrefBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false)
|
||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
|
||||||
|
|
||||||
parameters.split(" ")
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value
|
||||||
.map { it.split(",") }
|
|
||||||
.forEach {
|
builder.setMtu(VPN_MTU)
|
||||||
when (it[0][0]) {
|
builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)
|
||||||
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
|
//builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||||
's' -> builder.addSearchDomain(it[1])
|
if (routingMode == ERoutingMode.BYPASS_LAN.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
||||||
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
|
resources.getStringArray(R.array.bypass_private_ip_address).forEach {
|
||||||
'r' -> {
|
val addr = it.split('/')
|
||||||
if (routingMode == "1" || routingMode == "3") {
|
|
||||||
if (it[1] == "::") { //not very elegant, should move Vpn setting in Kotlin, simplify go code
|
|
||||||
builder.addRoute("2000::", 3)
|
|
||||||
} else {
|
|
||||||
resources.getStringArray(R.array.bypass_private_ip_address).forEach { cidr ->
|
|
||||||
val addr = cidr.split('/')
|
|
||||||
builder.addRoute(addr[0], addr[1].toInt())
|
builder.addRoute(addr[0], addr[1].toInt())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
builder.addRoute(it[1], Integer.parseInt(it[2]))
|
builder.addRoute("0.0.0.0", 0)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
'd' -> builder.addDnsServer(it[1])
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
||||||
|
builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)
|
||||||
|
if (routingMode == ERoutingMode.BYPASS_LAN.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
||||||
|
builder.addRoute("2000::", 3) //currently only 1/8 of total ipV6 is in use
|
||||||
|
} else {
|
||||||
|
builder.addRoute("::", 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!enableLocalDns) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||||
|
builder.addDnsServer(PRIVATE_VLAN4_ROUTER)
|
||||||
|
} else {
|
||||||
Utils.getVpnDnsServers()
|
Utils.getVpnDnsServers()
|
||||||
.forEach {
|
.forEach {
|
||||||
if (Utils.isPureIpAddress(it)) {
|
if (Utils.isPureIpAddress(it)) {
|
||||||
@@ -132,8 +146,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
|
|
||||||
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
|
||||||
settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
|
|
||||||
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
|
||||||
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
|
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
|
||||||
apps?.forEach {
|
apps?.forEach {
|
||||||
@@ -170,23 +183,59 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
// Create a new interface using the builder and save the parameters.
|
// Create a new interface using the builder and save the parameters.
|
||||||
try {
|
try {
|
||||||
mInterface = builder.establish()!!
|
mInterface = builder.establish()!!
|
||||||
|
runTun2socks()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// non-nullable lateinit var
|
// non-nullable lateinit var
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
stopV2Ray()
|
stopV2Ray()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runTun2socks() {
|
||||||
|
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||||
|
val cmd = arrayListOf(File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath,
|
||||||
|
"--netif-ipaddr", PRIVATE_VLAN4_ROUTER,
|
||||||
|
"--netif-netmask", "255.255.255.252",
|
||||||
|
"--socks-server-addr", "127.0.0.1:${socksPort}",
|
||||||
|
"--tunmtu", VPN_MTU.toString(),
|
||||||
|
"--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath,
|
||||||
|
"--enable-udprelay",
|
||||||
|
"--loglevel", "notice")
|
||||||
|
|
||||||
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PREFER_IPV6) == true) {
|
||||||
|
cmd.add("--netif-ip6addr")
|
||||||
|
cmd.add(PRIVATE_VLAN6_ROUTER)
|
||||||
|
}
|
||||||
|
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
|
||||||
|
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
||||||
|
cmd.add("--dnsgw")
|
||||||
|
cmd.add("127.0.0.1:${localDnsPort}")
|
||||||
|
}
|
||||||
|
Log.d(packageName, cmd.toString())
|
||||||
|
|
||||||
|
try {
|
||||||
|
val proBuilder = ProcessBuilder(cmd)
|
||||||
|
proBuilder.redirectErrorStream(true)
|
||||||
|
process = proBuilder
|
||||||
|
.directory(applicationContext.filesDir)
|
||||||
|
.start()
|
||||||
|
Log.d(packageName, process.toString())
|
||||||
|
|
||||||
sendFd()
|
sendFd()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(packageName, e.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendFd() {
|
private fun sendFd() {
|
||||||
val fd = mInterface.fileDescriptor
|
val fd = mInterface.fileDescriptor
|
||||||
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
|
val path = File(applicationContext.filesDir, "sock_path").absolutePath
|
||||||
|
Log.d(packageName, path)
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var tries = 0
|
var tries = 0
|
||||||
while (true) try {
|
while (true) try {
|
||||||
Thread.sleep(1000L shl tries)
|
Thread.sleep(50L shl tries)
|
||||||
Log.d(packageName, "sendFd tries: $tries")
|
Log.d(packageName, "sendFd tries: $tries")
|
||||||
LocalSocket().use { localSocket ->
|
LocalSocket().use { localSocket ->
|
||||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||||
@@ -221,6 +270,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.d(packageName, "tun2socks destroy")
|
||||||
|
process.destroy()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(packageName, e.toString())
|
||||||
|
}
|
||||||
|
|
||||||
V2RayServiceManager.stopV2rayPoint()
|
V2RayServiceManager.stopV2rayPoint()
|
||||||
|
|
||||||
if (isForced) {
|
if (isForced) {
|
||||||
@@ -236,7 +292,6 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,8 +299,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startService(parameters: String) {
|
override fun startService() {
|
||||||
setup(parameters)
|
setup()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopService() {
|
override fun stopService() {
|
||||||
@@ -256,4 +311,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
|
|||||||
return protect(socket)
|
return protect(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
|
val context = newBase?.let {
|
||||||
|
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
||||||
|
}
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import com.v2ray.ang.util.MyContextWrapper
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
|
||||||
abstract class BaseActivity : AppCompatActivity() {
|
abstract class BaseActivity : AppCompatActivity() {
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
@@ -11,4 +18,36 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
checkDarkMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkDarkMode() {
|
||||||
|
if (Utils.getDarkModeStatus(this)) {
|
||||||
|
if (this.javaClass.simpleName == "MainActivity") {
|
||||||
|
setTheme(R.style.AppThemeDark_NoActionBar)
|
||||||
|
} else {
|
||||||
|
setTheme(R.style.AppThemeDark)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.javaClass.simpleName == "MainActivity") {
|
||||||
|
setTheme(R.style.AppThemeLight_NoActionBar)
|
||||||
|
} else {
|
||||||
|
setTheme(R.style.AppThemeLight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
override fun attachBaseContext(newBase: Context?) {
|
||||||
|
val context = newBase?.let {
|
||||||
|
MyContextWrapper.wrap(newBase, Utils.getLocale(newBase))
|
||||||
|
}
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.os.Bundle
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||||
@@ -15,6 +16,7 @@ import com.v2ray.ang.util.Utils
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.LinkedHashSet
|
import java.util.LinkedHashSet
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ class LogcatActivity : BaseActivity() {
|
|||||||
try {
|
try {
|
||||||
binding.pbWaiting.visibility = View.VISIBLE
|
binding.pbWaiting.visibility = View.VISIBLE
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
lifecycleScope.launch(Dispatchers.Default) {
|
||||||
if (shouldFlushLog) {
|
if (shouldFlushLog) {
|
||||||
val lst = LinkedHashSet<String>()
|
val lst = LinkedHashSet<String>()
|
||||||
lst.add("logcat")
|
lst.add("logcat")
|
||||||
@@ -70,7 +72,7 @@ class LogcatActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_logcat, menu)
|
menuInflater.inflate(R.menu.menu_logcat, menu)
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
@@ -81,7 +83,7 @@ class LogcatActivity : BaseActivity() {
|
|||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.delete -> {
|
R.id.clear_all -> {
|
||||||
logcat(true)
|
logcat(true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,47 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.Intent
|
import android.content.*
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
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.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.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import com.google.android.material.navigation.NavigationView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.GravityCompat
|
||||||
|
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.BuildConfig
|
import com.v2ray.ang.BuildConfig
|
||||||
import com.v2ray.ang.R
|
|
||||||
import com.v2ray.ang.databinding.ActivityMainBinding
|
import com.v2ray.ang.databinding.ActivityMainBinding
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
|
||||||
import com.v2ray.ang.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.Observable
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import java.net.URL
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||||
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
|
import com.v2ray.ang.util.*
|
||||||
|
import com.v2ray.ang.viewmodel.MainViewModel
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import me.drakeet.support.toast.ToastCompat
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
@@ -81,7 +81,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
binding.layoutTest.setOnClickListener {
|
binding.layoutTest.setOnClickListener {
|
||||||
if (mainViewModel.isRunning.value == true) {
|
if (mainViewModel.isRunning.value == true) {
|
||||||
binding.tvTestState.text = getString(R.string.connection_test_testing)
|
setTestState(getString(R.string.connection_test_testing))
|
||||||
mainViewModel.testCurrentServerRealPing()
|
mainViewModel.testCurrentServerRealPing()
|
||||||
} else {
|
} else {
|
||||||
// tv_test_state.text = getString(R.string.connection_test_fail)
|
// tv_test_state.text = getString(R.string.connection_test_fail)
|
||||||
@@ -102,41 +102,63 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
binding.drawerLayout.addDrawerListener(toggle)
|
binding.drawerLayout.addDrawerListener(toggle)
|
||||||
toggle.syncState()
|
toggle.syncState()
|
||||||
binding.navView.setNavigationItemSelectedListener(this)
|
binding.navView.setNavigationItemSelectedListener(this)
|
||||||
binding.version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
|
binding.version.text = "v${BuildConfig.VERSION_NAME} (${SpeedtestUtil.getLibVersion()})"
|
||||||
|
|
||||||
setupViewModelObserver()
|
setupViewModel()
|
||||||
|
copyAssets()
|
||||||
migrateLegacy()
|
migrateLegacy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupViewModelObserver() {
|
private fun setupViewModel() {
|
||||||
mainViewModel.updateListAction.observe(this, {
|
mainViewModel.updateListAction.observe(this) { index ->
|
||||||
val index = it ?: return@observe
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
adapter.notifyItemChanged(index)
|
adapter.notifyItemChanged(index)
|
||||||
} else {
|
} else {
|
||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
mainViewModel.updateTestResultAction.observe(this, { binding.tvTestState.text = it })
|
mainViewModel.updateTestResultAction.observe(this) { setTestState(it) }
|
||||||
mainViewModel.isRunning.observe(this, {
|
mainViewModel.isRunning.observe(this) { isRunning ->
|
||||||
val isRunning = it ?: return@observe
|
|
||||||
adapter.isRunning = isRunning
|
adapter.isRunning = isRunning
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
binding.fab.setImageResource(R.drawable.ic_v)
|
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorSelected))
|
||||||
binding.tvTestState.text = getString(R.string.connection_connected)
|
setTestState(getString(R.string.connection_connected))
|
||||||
binding.layoutTest.isFocusable = true
|
binding.layoutTest.isFocusable = true
|
||||||
} else {
|
} else {
|
||||||
binding.fab.setImageResource(R.drawable.ic_v_idle)
|
binding.fab.backgroundTintList = ColorStateList.valueOf(ContextCompat.getColor(this, R.color.colorUnselected))
|
||||||
binding.tvTestState.text = getString(R.string.connection_not_connected)
|
setTestState(getString(R.string.connection_not_connected))
|
||||||
binding.layoutTest.isFocusable = false
|
binding.layoutTest.isFocusable = false
|
||||||
}
|
}
|
||||||
hideCircle()
|
hideCircle()
|
||||||
})
|
}
|
||||||
mainViewModel.startListenBroadcast()
|
mainViewModel.startListenBroadcast()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun copyAssets() {
|
||||||
|
val extFolder = Utils.userAssetPath(this)
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val geo = arrayOf("geosite.dat", "geoip.dat")
|
||||||
|
assets.list("")
|
||||||
|
?.filter { geo.contains(it) }
|
||||||
|
?.filter { !File(extFolder, it).exists() }
|
||||||
|
?.forEach {
|
||||||
|
val target = File(extFolder, it)
|
||||||
|
assets.open(it).use { input ->
|
||||||
|
FileOutputStream(target).use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.i(ANG_PACKAGE, "Copied from apk assets folder to ${target.absolutePath}")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(ANG_PACKAGE, "asset copy failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun migrateLegacy() {
|
private fun migrateLegacy() {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
|
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -161,6 +183,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
hideCircle()
|
hideCircle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun restartV2Ray() {
|
||||||
|
if (mainViewModel.isRunning.value == true) {
|
||||||
|
Utils.stopVService(this)
|
||||||
|
}
|
||||||
|
Observable.timer(500, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe {
|
||||||
|
startV2Ray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override fun onResume() {
|
public override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
mainViewModel.reloadServerList()
|
mainViewModel.reloadServerList()
|
||||||
@@ -170,7 +203,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
super.onPause()
|
super.onPause()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_main, menu)
|
menuInflater.inflate(R.menu.menu_main, menu)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -185,18 +218,23 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_manually_vmess -> {
|
R.id.import_manually_vmess -> {
|
||||||
startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
|
importManually(EConfigType.VMESS.value)
|
||||||
setClass(this, ServerActivity::class.java))
|
true
|
||||||
|
}
|
||||||
|
R.id.import_manually_vless -> {
|
||||||
|
importManually(EConfigType.VLESS.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_manually_ss -> {
|
R.id.import_manually_ss -> {
|
||||||
startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
|
importManually(EConfigType.SHADOWSOCKS.value)
|
||||||
setClass(this, ServerActivity::class.java))
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_manually_socks -> {
|
R.id.import_manually_socks -> {
|
||||||
startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
|
importManually(EConfigType.SOCKS.value)
|
||||||
setClass(this, ServerActivity::class.java))
|
true
|
||||||
|
}
|
||||||
|
R.id.import_manually_trojan -> {
|
||||||
|
importManually(EConfigType.TROJAN.value)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.import_config_custom_clipboard -> {
|
R.id.import_config_custom_clipboard -> {
|
||||||
@@ -240,17 +278,56 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
// R.id.settings -> {
|
R.id.real_ping_all -> {
|
||||||
// startActivity<SettingsActivity>("isRunning" to isRunning)
|
mainViewModel.testAllRealPing()
|
||||||
// true
|
true
|
||||||
// }
|
}
|
||||||
// R.id.logcat -> {
|
|
||||||
// startActivity<LogcatActivity>()
|
R.id.service_restart -> {
|
||||||
// true
|
restartV2Ray()
|
||||||
// }
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.del_all_config -> {
|
||||||
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
MmkvManager.removeAllServer()
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.del_invalid_config -> {
|
||||||
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
MmkvManager.removeInvalidServer()
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.sort_by_test_results -> {
|
||||||
|
MmkvManager.sortByTestResults()
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.filter_config -> {
|
||||||
|
mainViewModel.filterConfig(this)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun importManually(createConfigType : Int) {
|
||||||
|
startActivity(
|
||||||
|
Intent()
|
||||||
|
.putExtra("createConfigType", createConfigType)
|
||||||
|
.putExtra("subscriptionId", mainViewModel.subscriptionId)
|
||||||
|
.setClass(this, ServerActivity::class.java)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* import config from qrcode
|
* import config from qrcode
|
||||||
@@ -304,9 +381,16 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun importBatchConfig(server: String?, subid: String = "") {
|
fun importBatchConfig(server: String?, subid: String = "") {
|
||||||
var count = AngConfigManager.importBatchConfig(server, subid)
|
val subid2 = if(subid.isNullOrEmpty()){
|
||||||
|
mainViewModel.subscriptionId
|
||||||
|
}else{
|
||||||
|
subid
|
||||||
|
}
|
||||||
|
val append = subid.isNullOrEmpty()
|
||||||
|
|
||||||
|
var count = AngConfigManager.importBatchConfig(server, subid2, append)
|
||||||
if (count <= 0) {
|
if (count <= 0) {
|
||||||
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid)
|
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid2, append)
|
||||||
}
|
}
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
@@ -369,7 +453,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
toast(R.string.toast_invalid_url)
|
toast(R.string.toast_invalid_url)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val configText = try {
|
val configText = try {
|
||||||
Utils.getUrlContentWithCustomUserAgent(url)
|
Utils.getUrlContentWithCustomUserAgent(url)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -401,12 +485,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
) {
|
) {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
|
if (!it.second.enabled) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
val url = it.second.url
|
val url = it.second.url
|
||||||
if (!Utils.isValidUrl(url)) {
|
if (!Utils.isValidUrl(url)) {
|
||||||
return@forEach
|
return@forEach
|
||||||
}
|
}
|
||||||
Log.d(ANG_PACKAGE, url)
|
Log.d(ANG_PACKAGE, url)
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val configText = try {
|
val configText = try {
|
||||||
Utils.getUrlContentWithCustomUserAgent(url)
|
Utils.getUrlContentWithCustomUserAgent(url)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -417,7 +504,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
importBatchConfig(Utils.decode(configText), it.first)
|
importBatchConfig(configText, it.first)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -438,7 +525,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
|
||||||
} catch (ex: android.content.ActivityNotFoundException) {
|
} catch (ex: ActivityNotFoundException) {
|
||||||
toast(R.string.toast_require_file_manager)
|
toast(R.string.toast_require_file_manager)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,8 +567,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
mainViewModel.appendCustomConfigServer(server)
|
mainViewModel.appendCustomConfigServer(server)
|
||||||
|
mainViewModel.reloadServerList()
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
//adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -489,6 +577,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setTestState(content: String?) {
|
||||||
|
binding.tvTestState.text = content
|
||||||
|
}
|
||||||
|
|
||||||
// val mConnection = object : ServiceConnection {
|
// val mConnection = object : ServiceConnection {
|
||||||
// override fun onServiceDisconnected(name: ComponentName?) {
|
// override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
// }
|
// }
|
||||||
@@ -515,11 +607,16 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
Observable.timer(300, TimeUnit.MILLISECONDS)
|
Observable.timer(300, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
|
try {
|
||||||
if (binding.fabProgressCircle.isShown) {
|
if (binding.fabProgressCircle.isShown) {
|
||||||
binding.fabProgressCircle.hide()
|
binding.fabProgressCircle.hide()
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(ANG_PACKAGE, e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.d(ANG_PACKAGE, e.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,14 +639,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
startActivity(Intent(this, SettingsActivity::class.java)
|
startActivity(Intent(this, SettingsActivity::class.java)
|
||||||
.putExtra("isRunning", mainViewModel.isRunning.value == true))
|
.putExtra("isRunning", mainViewModel.isRunning.value == true))
|
||||||
}
|
}
|
||||||
|
R.id.user_asset_setting -> {
|
||||||
|
startActivity(Intent(this, UserAssetActivity::class.java))
|
||||||
|
}
|
||||||
R.id.feedback -> {
|
R.id.feedback -> {
|
||||||
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
||||||
}
|
}
|
||||||
R.id.promotion -> {
|
R.id.promotion -> {
|
||||||
Utils.openUri(this, AppConfig.promotionUrl)
|
Utils.openUri(this, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
|
||||||
}
|
|
||||||
R.id.donate -> {
|
|
||||||
// startActivity<InappBuyActivity>()
|
|
||||||
}
|
}
|
||||||
R.id.logcat -> {
|
R.id.logcat -> {
|
||||||
startActivity(Intent(this, LogcatActivity::class.java))
|
startActivity(Intent(this, LogcatActivity::class.java))
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package com.v2ray.ang.ui
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
@@ -38,29 +38,43 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
private var mActivity: MainActivity = activity
|
private var mActivity: MainActivity = activity
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
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 subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val share_method: Array<out String> by lazy {
|
private val share_method: Array<out String> by lazy {
|
||||||
mActivity.resources.getStringArray(R.array.share_method)
|
mActivity.resources.getStringArray(R.array.share_method)
|
||||||
}
|
}
|
||||||
var isRunning = false
|
var isRunning = false
|
||||||
|
|
||||||
override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
|
override fun getItemCount() = mActivity.mainViewModel.serversCache.size + 1
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||||
if (holder is MainViewHolder) {
|
if (holder is MainViewHolder) {
|
||||||
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
val guid = mActivity.mainViewModel.serversCache[position].guid
|
||||||
val config = mActivity.mainViewModel.serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?: return
|
val config = mActivity.mainViewModel.serversCache[position].config
|
||||||
|
// //filter
|
||||||
|
// if (mActivity.mainViewModel.subscriptionId.isNotEmpty()
|
||||||
|
// && mActivity.mainViewModel.subscriptionId != config.subscriptionId
|
||||||
|
// ) {
|
||||||
|
// holder.itemMainBinding.cardView.visibility = View.GONE
|
||||||
|
// } else {
|
||||||
|
// holder.itemMainBinding.cardView.visibility = View.VISIBLE
|
||||||
|
// }
|
||||||
|
|
||||||
val outbound = config.getProxyOutbound()
|
val outbound = config.getProxyOutbound()
|
||||||
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
|
||||||
|
|
||||||
holder.itemMainBinding.tvName.text = config.remarks
|
holder.itemMainBinding.tvName.text = config.remarks
|
||||||
holder.itemMainBinding.btnRadio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
|
||||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
|
||||||
if (aff?.testDelayMillis?:0L < 0L) {
|
if ((aff?.testDelayMillis ?: 0L) < 0L) {
|
||||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
|
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPingRed))
|
||||||
} else {
|
} else {
|
||||||
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
|
||||||
}
|
}
|
||||||
|
if (guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
|
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorSelected)
|
||||||
|
} else {
|
||||||
|
holder.itemMainBinding.layoutIndicator.setBackgroundResource(R.color.colorUnselected)
|
||||||
|
}
|
||||||
holder.itemMainBinding.tvSubscription.text = ""
|
holder.itemMainBinding.tvSubscription.text = ""
|
||||||
val json = subStorage?.decodeString(config.subscriptionId)
|
val json = subStorage?.decodeString(config.subscriptionId)
|
||||||
if (!json.isNullOrBlank()) {
|
if (!json.isNullOrBlank()) {
|
||||||
@@ -69,14 +83,18 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
|
|
||||||
var shareOptions = share_method.asList()
|
var shareOptions = share_method.asList()
|
||||||
if (config.configType == EConfigType.CUSTOM) {
|
when (config.configType) {
|
||||||
|
EConfigType.CUSTOM -> {
|
||||||
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
|
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
|
||||||
shareOptions = shareOptions.takeLast(1)
|
shareOptions = shareOptions.takeLast(1)
|
||||||
} else if (config.configType == EConfigType.VLESS) {
|
}
|
||||||
|
EConfigType.VLESS -> {
|
||||||
holder.itemMainBinding.tvType.text = config.configType.name
|
holder.itemMainBinding.tvType.text = config.configType.name
|
||||||
} else {
|
}
|
||||||
|
else -> {
|
||||||
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
holder.itemMainBinding.tvStatistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
|
holder.itemMainBinding.tvStatistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
|
||||||
|
|
||||||
holder.itemMainBinding.layoutShare.setOnClickListener {
|
holder.itemMainBinding.layoutShare.setOnClickListener {
|
||||||
@@ -119,9 +137,15 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
holder.itemMainBinding.layoutRemove.setOnClickListener {
|
||||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
mActivity.mainViewModel.removeServer(guid)
|
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||||
notifyItemRemoved(position)
|
AlertDialog.Builder(mActivity).setMessage(R.string.del_config_comfirm)
|
||||||
notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
removeServer(guid, position)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else {
|
||||||
|
removeServer(guid, position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,8 +153,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
|
||||||
if (guid != selected) {
|
if (guid != selected) {
|
||||||
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
|
||||||
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(selected!!))
|
||||||
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
|
notifyItemChanged(mActivity.mainViewModel.getPosition(guid))
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
mActivity.showCircle()
|
mActivity.showCircle()
|
||||||
Utils.stopVService(mActivity)
|
Utils.stopVService(mActivity)
|
||||||
@@ -164,6 +188,12 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeServer(guid: String,position:Int) {
|
||||||
|
mActivity.mainViewModel.removeServer(guid)
|
||||||
|
notifyItemRemoved(position)
|
||||||
|
notifyItemRangeChanged(position, mActivity.mainViewModel.serversCache.size)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||||
return when (viewType) {
|
return when (viewType) {
|
||||||
VIEW_TYPE_ITEM ->
|
VIEW_TYPE_ITEM ->
|
||||||
@@ -174,7 +204,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return if (position == mActivity.mainViewModel.serverList.size) {
|
return if (position == mActivity.mainViewModel.serversCache.size) {
|
||||||
VIEW_TYPE_FOOTER
|
VIEW_TYPE_FOOTER
|
||||||
} else {
|
} else {
|
||||||
VIEW_TYPE_ITEM
|
VIEW_TYPE_ITEM
|
||||||
@@ -198,7 +228,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
|||||||
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder
|
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder
|
||||||
|
|
||||||
override fun onItemDismiss(position: Int) {
|
override fun onItemDismiss(position: Int) {
|
||||||
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
|
val guid = mActivity.mainViewModel.serversCache.getOrNull(position)?.guid ?: return
|
||||||
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
// mActivity.alert(R.string.del_config_comfirm) {
|
// mActivity.alert(R.string.del_config_comfirm) {
|
||||||
// positiveButton(android.R.string.ok) {
|
// positiveButton(android.R.string.ok) {
|
||||||
|
|||||||
@@ -1,39 +1,32 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.animation.Animator
|
|
||||||
import android.animation.AnimatorListenerAdapter
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
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.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.animation.AccelerateInterpolator
|
import androidx.appcompat.widget.SearchView
|
||||||
import android.view.animation.DecelerateInterpolator
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.v2ray.ang.R
|
import androidx.preference.PreferenceManager
|
||||||
import com.v2ray.ang.util.AppManagerUtil
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import rx.schedulers.Schedulers
|
|
||||||
import java.text.Collator
|
|
||||||
import java.util.*
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityBypassListBinding
|
import com.v2ray.ang.databinding.ActivityBypassListBinding
|
||||||
import com.v2ray.ang.dto.AppInfo
|
import com.v2ray.ang.dto.AppInfo
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.extension.v2RayApplication
|
import com.v2ray.ang.extension.v2RayApplication
|
||||||
|
import com.v2ray.ang.util.AppManagerUtil
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
|
import rx.schedulers.Schedulers
|
||||||
|
import java.text.Collator
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class PerAppProxyActivity : BaseActivity() {
|
class PerAppProxyActivity : BaseActivity() {
|
||||||
private lateinit var binding: ActivityBypassListBinding
|
private lateinit var binding: ActivityBypassListBinding
|
||||||
@@ -66,8 +59,8 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
one.isSelected = 0
|
one.isSelected = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val comparator = object : Comparator<AppInfo> {
|
val comparator = Comparator<AppInfo> { p1, p2 ->
|
||||||
override fun compare(p1: AppInfo, p2: AppInfo): Int = when {
|
when {
|
||||||
p1.isSelected > p2.isSelected -> -1
|
p1.isSelected > p2.isSelected -> -1
|
||||||
p1.isSelected == p2.isSelected -> 0
|
p1.isSelected == p2.isSelected -> 0
|
||||||
else -> 1
|
else -> 1
|
||||||
@@ -96,17 +89,17 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
binding.recyclerView.adapter = adapter
|
binding.recyclerView.adapter = adapter
|
||||||
binding.pbWaiting.visibility = View.GONE
|
binding.pbWaiting.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
/***
|
||||||
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
var dst = 0
|
var dst = 0
|
||||||
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3
|
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 2
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
dst += dy
|
dst += dy
|
||||||
if (dst > threshold) {
|
if (dst > threshold) {
|
||||||
binding.headerView.hide()
|
header_view.hide()
|
||||||
dst = 0
|
dst = 0
|
||||||
} else if (dst < -20) {
|
} else if (dst < -20) {
|
||||||
binding.headerView.show()
|
header_view.show()
|
||||||
dst = 0
|
dst = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,6 +134,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
showing = true
|
showing = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
***/
|
||||||
|
|
||||||
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
|
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
|
||||||
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
|
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
|
||||||
@@ -152,13 +146,14 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
binding.switchBypassApps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
|
binding.switchBypassApps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
|
||||||
|
|
||||||
binding.etSearch.setOnEditorActionListener { v, actionId, _ ->
|
/***
|
||||||
|
et_search.setOnEditorActionListener { v, actionId, event ->
|
||||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||||
//hide
|
//hide
|
||||||
var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
|
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
|
||||||
|
|
||||||
val key = v.text.toString().uppercase()
|
val key = v.text.toString().toUpperCase()
|
||||||
val apps = ArrayList<AppInfo>()
|
val apps = ArrayList<AppInfo>()
|
||||||
if (TextUtils.isEmpty(key)) {
|
if (TextUtils.isEmpty(key)) {
|
||||||
appsAll?.forEach {
|
appsAll?.forEach {
|
||||||
@@ -166,19 +161,20 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
appsAll?.forEach {
|
appsAll?.forEach {
|
||||||
if (it.appName.uppercase().indexOf(key) >= 0) {
|
if (it.appName.toUpperCase().indexOf(key) >= 0) {
|
||||||
apps.add(it)
|
apps.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
|
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
|
||||||
binding.recyclerView.adapter = adapter
|
recycler_view.adapter = adapter
|
||||||
adapter?.notifyDataSetChanged()
|
adapter?.notifyDataSetChanged()
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
***/
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
@@ -188,8 +184,25 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_bypass_list, menu)
|
menuInflater.inflate(R.menu.menu_bypass_list, menu)
|
||||||
|
|
||||||
|
val searchItem = menu.findItem(R.id.search_view)
|
||||||
|
if (searchItem != null) {
|
||||||
|
val searchView = searchItem.actionView as SearchView
|
||||||
|
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(newText: String?): Boolean {
|
||||||
|
filterProxyApp(newText!!)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,14 +219,20 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist!!.add(packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
it.notifyDataSetChanged()
|
it.notifyDataSetChanged()
|
||||||
true
|
true
|
||||||
} ?: false
|
} ?: false
|
||||||
R.id.select_proxy_app -> {
|
R.id.select_proxy_app -> {
|
||||||
selectProxyApp()
|
selectProxyApp()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.import_proxy_app -> {
|
||||||
|
importProxyApp()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.export_proxy_app -> {
|
||||||
|
exportProxyApp()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
@@ -222,27 +241,41 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
private fun selectProxyApp() {
|
private fun selectProxyApp() {
|
||||||
toast(R.string.msg_downloading_content)
|
toast(R.string.msg_downloading_content)
|
||||||
val url = AppConfig.androidpackagenamelistUrl
|
val url = AppConfig.androidpackagenamelistUrl
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val content = try {
|
val content = Utils.getUrlContext(url, 5000)
|
||||||
URL(url).readText()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
""
|
|
||||||
}
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
Log.d(ANG_PACKAGE, content)
|
Log.d(ANG_PACKAGE, content)
|
||||||
selectProxyApp(content)
|
selectProxyApp(content, true)
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun selectProxyApp(content: String): Boolean {
|
private fun importProxyApp() {
|
||||||
try {
|
val content = Utils.getClipboard(applicationContext)
|
||||||
var proxyApps = content
|
|
||||||
if (TextUtils.isEmpty(content)) {
|
if (TextUtils.isEmpty(content)) {
|
||||||
val assets = Utils.readTextFromAssets(v2RayApplication, "proxy_packagename.txt")
|
return
|
||||||
proxyApps = assets.lines().toString()
|
}
|
||||||
|
selectProxyApp(content, false)
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportProxyApp() {
|
||||||
|
var lst = binding.switchBypassApps.isChecked.toString()
|
||||||
|
|
||||||
|
adapter?.blacklist?.forEach block@{
|
||||||
|
lst = lst + System.getProperty("line.separator") + it
|
||||||
|
}
|
||||||
|
Utils.setClipboard(applicationContext, lst)
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectProxyApp(content: String, force: Boolean): Boolean {
|
||||||
|
try {
|
||||||
|
val proxyApps = if (TextUtils.isEmpty(content)) {
|
||||||
|
Utils.readTextFromAssets(v2RayApplication, "proxy_packagename.txt")
|
||||||
|
} else {
|
||||||
|
content
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(proxyApps)) {
|
if (TextUtils.isEmpty(proxyApps)) {
|
||||||
return false
|
return false
|
||||||
@@ -255,7 +288,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
it.apps.forEach block@{
|
it.apps.forEach block@{
|
||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
Log.d(ANG_PACKAGE, packageName)
|
Log.d(ANG_PACKAGE, packageName)
|
||||||
if (proxyApps.indexOf(packageName) < 0) {
|
if (!inProxyApps(proxyApps, packageName, force)) {
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist!!.add(packageName)
|
||||||
println(packageName)
|
println(packageName)
|
||||||
return@block
|
return@block
|
||||||
@@ -268,7 +301,7 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
it.apps.forEach block@{
|
it.apps.forEach block@{
|
||||||
val packageName = it.packageName
|
val packageName = it.packageName
|
||||||
Log.d(ANG_PACKAGE, packageName)
|
Log.d(ANG_PACKAGE, packageName)
|
||||||
if (proxyApps.indexOf(packageName) >= 0) {
|
if (inProxyApps(proxyApps, packageName, force)) {
|
||||||
adapter?.blacklist!!.add(packageName)
|
adapter?.blacklist!!.add(packageName)
|
||||||
println(packageName)
|
println(packageName)
|
||||||
return@block
|
return@block
|
||||||
@@ -283,4 +316,40 @@ class PerAppProxyActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun inProxyApps(proxyApps: String, packageName: String, force: Boolean): Boolean {
|
||||||
|
if (force) {
|
||||||
|
if (packageName == "com.google.android.webview") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (packageName.startsWith("com.google")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyApps.indexOf(packageName) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun filterProxyApp(content: String): Boolean {
|
||||||
|
val apps = ArrayList<AppInfo>()
|
||||||
|
|
||||||
|
val key = content.uppercase()
|
||||||
|
if (key.isNotEmpty()) {
|
||||||
|
appsAll?.forEach {
|
||||||
|
if (it.appName.uppercase().indexOf(key) >= 0
|
||||||
|
|| it.packageName.uppercase().indexOf(key) >= 0) {
|
||||||
|
apps.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
appsAll?.forEach {
|
||||||
|
apps.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
|
||||||
|
binding.recyclerView.adapter = adapter
|
||||||
|
adapter?.notifyDataSetChanged()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -36,7 +35,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
|||||||
VIEW_TYPE_HEADER -> {
|
VIEW_TYPE_HEADER -> {
|
||||||
val view = View(ctx)
|
val view = View(ctx)
|
||||||
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
view.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3)
|
ctx.resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 0)
|
||||||
BaseViewHolder(view)
|
BaseViewHolder(view)
|
||||||
}
|
}
|
||||||
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
||||||
@@ -66,10 +65,10 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
|||||||
itemBypassBinding.packageName.text = appInfo.packageName
|
itemBypassBinding.packageName.text = appInfo.packageName
|
||||||
if (appInfo.isSystemApp) {
|
if (appInfo.isSystemApp) {
|
||||||
itemBypassBinding.name.text = String.format("** %1s", appInfo.appName)
|
itemBypassBinding.name.text = String.format("** %1s", appInfo.appName)
|
||||||
itemBypassBinding.name.setTextColor(Color.RED)
|
//name.textColor = Color.RED
|
||||||
} else {
|
} else {
|
||||||
itemBypassBinding.name.text = appInfo.appName
|
itemBypassBinding.name.text = appInfo.appName
|
||||||
itemBypassBinding.name.setTextColor(Color.DKGRAY)
|
//name.textColor = Color.DKGRAY
|
||||||
}
|
}
|
||||||
|
|
||||||
itemView.setOnClickListener(this)
|
itemView.setOnClickListener(this)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
@@ -21,7 +20,7 @@ class RoutingSettingsActivity : BaseActivity() {
|
|||||||
val view = binding.root
|
val view = binding.root
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
|
|
||||||
title = getString(R.string.routing_settings_title)
|
title = getString(R.string.title_pref_routing_custom)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
val fragments = ArrayList<Fragment>()
|
val fragments = ArrayList<Fragment>()
|
||||||
@@ -31,7 +30,7 @@ class RoutingSettingsActivity : BaseActivity() {
|
|||||||
|
|
||||||
val adapter = FragmentAdapter(this, fragments)
|
val adapter = FragmentAdapter(this, fragments)
|
||||||
binding.viewpager.adapter = adapter
|
binding.viewpager.adapter = adapter
|
||||||
binding.tablayout.setTabTextColors(Color.BLACK, Color.RED)
|
//tablayout.setTabTextColors(Color.BLACK, Color.RED)
|
||||||
TabLayoutMediator(binding.tablayout, binding.viewpager) { tab, position ->
|
TabLayoutMediator(binding.tablayout, binding.viewpager) { tab, position ->
|
||||||
tab.text = titles[position]
|
tab.text = titles[position]
|
||||||
}.attach()
|
}.attach()
|
||||||
|
|||||||
@@ -4,33 +4,36 @@ import android.Manifest
|
|||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import android.text.TextUtils
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import com.v2ray.ang.R
|
|
||||||
import com.v2ray.ang.util.Utils
|
|
||||||
import kotlinx.android.synthetic.main.fragment_routing_settings.*
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.tbruyelle.rxpermissions.RxPermissions
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding
|
||||||
import com.v2ray.ang.extension.toast
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.URL
|
|
||||||
|
|
||||||
class RoutingSettingsFragment : Fragment() {
|
class RoutingSettingsFragment : Fragment() {
|
||||||
|
private lateinit var binding: FragmentRoutingSettingsBinding
|
||||||
companion object {
|
companion object {
|
||||||
private const val routing_arg = "routing_arg"
|
private const val routing_arg = "routing_arg"
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(context) }
|
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) }
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
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 {
|
fun newInstance(arg: String): Fragment {
|
||||||
@@ -45,7 +48,7 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
val content = defaultSharedPreferences.getString(requireArguments().getString(routing_arg), "")
|
val content = defaultSharedPreferences.getString(requireArguments().getString(routing_arg), "")
|
||||||
et_routing_content.text = Utils.getEditable(content!!)
|
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
||||||
|
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
@@ -57,13 +60,11 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.save_routing -> {
|
R.id.save_routing -> {
|
||||||
val content = et_routing_content.text.toString()
|
saveRouting()
|
||||||
defaultSharedPreferences.edit().putString(requireArguments().getString(routing_arg), content).apply()
|
|
||||||
activity?.toast(R.string.toast_success)
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.del_routing -> {
|
R.id.del_routing -> {
|
||||||
et_routing_content.text = null
|
binding.etRoutingContent.text = null
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.scan_replace -> {
|
R.id.scan_replace -> {
|
||||||
@@ -81,6 +82,12 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fun scanQRcode(forReplace: Boolean): Boolean {
|
||||||
// try {
|
// try {
|
||||||
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
|
||||||
@@ -105,42 +112,45 @@ class RoutingSettingsFragment : Fragment() {
|
|||||||
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||||
et_routing_content.text = Utils.getEditable(content!!)
|
binding.etRoutingContent.text = Utils.getEditable(content!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val scanQRCodeForAppend = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCodeForAppend = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val content = it.data?.getStringExtra("SCAN_RESULT")
|
val content = it.data?.getStringExtra("SCAN_RESULT")
|
||||||
et_routing_content.text = Utils.getEditable("${et_routing_content.text},$content")
|
binding.etRoutingContent.text = Utils.getEditable("${binding.etRoutingContent.text},$content")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDefaultRules(): Boolean {
|
fun setDefaultRules(): Boolean {
|
||||||
var url = AppConfig.v2rayCustomRoutingListUrl
|
var url = AppConfig.v2rayCustomRoutingListUrl
|
||||||
|
var tag = ""
|
||||||
when (requireArguments().getString(routing_arg)) {
|
when (requireArguments().getString(routing_arg)) {
|
||||||
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
|
||||||
url += AppConfig.TAG_AGENT
|
tag = AppConfig.TAG_AGENT
|
||||||
}
|
}
|
||||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
|
||||||
url += AppConfig.TAG_DIRECT
|
tag = AppConfig.TAG_DIRECT
|
||||||
}
|
}
|
||||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> {
|
AppConfig.PREF_V2RAY_ROUTING_BLOCKED -> {
|
||||||
url += AppConfig.TAG_BLOCKED
|
tag = AppConfig.TAG_BLOCKED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
url += tag
|
||||||
|
|
||||||
activity?.toast(R.string.msg_downloading_content)
|
activity?.toast(R.string.msg_downloading_content)
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val content = try {
|
val content = Utils.getUrlContext(url, 5000)
|
||||||
URL(url).readText()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
""
|
|
||||||
}
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
et_routing_content.text = Utils.getEditable(content)
|
val routingList = if (TextUtils.isEmpty(content)) {
|
||||||
activity?.toast(R.string.toast_success)
|
Utils.readTextFromAssets(activity?.v2RayApplication!!, "custom_routing_$tag")
|
||||||
|
} else {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
binding.etRoutingContent.text = Utils.getEditable(routingList)
|
||||||
|
saveRouting()
|
||||||
|
//toast(R.string.toast_success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class ScScannerActivity : BaseActivity() {
|
|||||||
|
|
||||||
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (it.resultCode == RESULT_OK) {
|
if (it.resultCode == RESULT_OK) {
|
||||||
val count = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "")
|
val count = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "", false)
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.menu_scanner, menu)
|
menuInflater.inflate(R.menu.menu_scanner, menu)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
|||||||
try {
|
try {
|
||||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||||
finished(text)
|
finished(text!!)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
toast(e.message.toString())
|
toast(e.message.toString())
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.*
|
||||||
import android.widget.ArrayAdapter
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.PREF_ALLOW_INSECURE
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
import com.v2ray.ang.dto.ServerConfig
|
import com.v2ray.ang.dto.ServerConfig
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_FLOW
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
|
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.XTLS
|
import com.v2ray.ang.dto.V2rayConfig.Companion.XTLS
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
@@ -21,19 +22,6 @@ import com.v2ray.ang.util.MmkvManager
|
|||||||
import com.v2ray.ang.util.MmkvManager.ID_MAIN
|
import com.v2ray.ang.util.MmkvManager.ID_MAIN
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import kotlinx.android.synthetic.main.activity_server_socks.*
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.*
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_address
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_id
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_path
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_port
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_remarks
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.et_request_host
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_allow_insecure
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_header_type_title
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_network
|
|
||||||
import kotlinx.android.synthetic.main.activity_server_vmess.sp_stream_security
|
|
||||||
|
|
||||||
class ServerActivity : BaseActivity() {
|
class ServerActivity : BaseActivity() {
|
||||||
|
|
||||||
@@ -48,6 +36,10 @@ class ServerActivity : BaseActivity() {
|
|||||||
private val createConfigType by lazy {
|
private val createConfigType by lazy {
|
||||||
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
|
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
|
||||||
}
|
}
|
||||||
|
private val subscriptionId by lazy {
|
||||||
|
intent.getStringExtra("subscriptionId")
|
||||||
|
}
|
||||||
|
|
||||||
private val securitys: Array<out String> by lazy {
|
private val securitys: Array<out String> by lazy {
|
||||||
resources.getStringArray(R.array.securitys)
|
resources.getStringArray(R.array.securitys)
|
||||||
}
|
}
|
||||||
@@ -76,6 +68,26 @@ class ServerActivity : BaseActivity() {
|
|||||||
resources.getStringArray(R.array.allowinsecures)
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
title = getString(R.string.title_server)
|
title = getString(R.string.title_server)
|
||||||
@@ -86,8 +98,8 @@ class ServerActivity : BaseActivity() {
|
|||||||
EConfigType.CUSTOM -> return
|
EConfigType.CUSTOM -> return
|
||||||
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
|
||||||
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
|
||||||
// EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
|
||||||
// EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
|
||||||
}
|
}
|
||||||
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
@@ -100,9 +112,9 @@ class ServerActivity : BaseActivity() {
|
|||||||
getString(R.string.server_lab_mode_type) else
|
getString(R.string.server_lab_mode_type) else
|
||||||
getString(R.string.server_lab_head_type)
|
getString(R.string.server_lab_head_type)
|
||||||
config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails ->
|
config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
sp_header_type.setSelection(Utils.arrayFind(types, transportDetails[0]))
|
sp_header_type?.setSelection(Utils.arrayFind(types, transportDetails[0]))
|
||||||
et_request_host.text = Utils.getEditable(transportDetails[1])
|
et_request_host?.text = Utils.getEditable(transportDetails[1])
|
||||||
et_path.text = Utils.getEditable(transportDetails[2])
|
et_path?.text = Utils.getEditable(transportDetails[2])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
@@ -130,12 +142,17 @@ class ServerActivity : BaseActivity() {
|
|||||||
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
|
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
|
||||||
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
|
et_alterId?.text = Utils.getEditable(outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString())
|
||||||
if (config.configType == EConfigType.SOCKS) {
|
if (config.configType == EConfigType.SOCKS) {
|
||||||
et_security.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
et_security?.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
|
||||||
} else if (config.configType == EConfigType.VLESS) {
|
} else if (config.configType == EConfigType.VLESS) {
|
||||||
et_security.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
|
et_security?.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
|
||||||
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
|
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
|
||||||
if (flow >= 0) {
|
if (flow >= 0) {
|
||||||
//sp_flow.setSelection(flow)
|
sp_flow?.setSelection(flow)
|
||||||
|
}
|
||||||
|
} else if (config.configType == EConfigType.TROJAN) {
|
||||||
|
val flow = Utils.arrayFind(flows, outbound.settings?.servers?.get(0)?.flow.orEmpty())
|
||||||
|
if (flow >= 0) {
|
||||||
|
sp_flow?.setSelection(flow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
|
||||||
@@ -152,7 +169,7 @@ class ServerActivity : BaseActivity() {
|
|||||||
if (allowinsecure >= 0) {
|
if (allowinsecure >= 0) {
|
||||||
sp_allow_insecure?.setSelection(allowinsecure)
|
sp_allow_insecure?.setSelection(allowinsecure)
|
||||||
}
|
}
|
||||||
et_request_host.text = Utils.getEditable(tlsSetting.serverName)
|
et_sni?.text = Utils.getEditable(tlsSetting.serverName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val network = Utils.arrayFind(networks, streamSetting.network)
|
val network = Utils.arrayFind(networks, streamSetting.network)
|
||||||
@@ -179,9 +196,10 @@ class ServerActivity : BaseActivity() {
|
|||||||
et_path?.text = null
|
et_path?.text = null
|
||||||
sp_stream_security?.setSelection(0)
|
sp_stream_security?.setSelection(0)
|
||||||
sp_allow_insecure?.setSelection(0)
|
sp_allow_insecure?.setSelection(0)
|
||||||
|
et_sni?.text = null
|
||||||
|
|
||||||
//et_security.text = null
|
//et_security.text = null
|
||||||
//sp_flow?.setSelection(0)
|
sp_flow?.setSelection(0)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +225,14 @@ class ServerActivity : BaseActivity() {
|
|||||||
toast(R.string.server_lab_id)
|
toast(R.string.server_lab_id)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
sp_stream_security?.let {
|
||||||
|
if (config.configType == EConfigType.TROJAN && TextUtils.isEmpty(streamSecuritys[it.selectedItemPosition])) {
|
||||||
|
toast(R.string.server_lab_stream_security)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
et_alterId?.let {
|
et_alterId?.let {
|
||||||
val alterId = Utils.parseInt(et_alterId.text.toString())
|
val alterId = Utils.parseInt(it.text.toString())
|
||||||
if (alterId < 0) {
|
if (alterId < 0) {
|
||||||
toast(R.string.server_lab_alterid)
|
toast(R.string.server_lab_alterid)
|
||||||
return false
|
return false
|
||||||
@@ -223,7 +247,10 @@ class ServerActivity : BaseActivity() {
|
|||||||
saveServers(server, port, config)
|
saveServers(server, port, config)
|
||||||
}
|
}
|
||||||
config.outboundBean?.streamSettings?.let {
|
config.outboundBean?.streamSettings?.let {
|
||||||
saveStreamSettings(it, config)
|
saveStreamSettings(it)
|
||||||
|
}
|
||||||
|
if(config.subscriptionId.isEmpty() && !subscriptionId.isNullOrEmpty()) {
|
||||||
|
config.subscriptionId = subscriptionId!!
|
||||||
}
|
}
|
||||||
|
|
||||||
MmkvManager.encodeServerConfig(editGuid, config)
|
MmkvManager.encodeServerConfig(editGuid, config)
|
||||||
@@ -237,13 +264,12 @@ class ServerActivity : BaseActivity() {
|
|||||||
vnext.port = port
|
vnext.port = port
|
||||||
vnext.users[0].id = et_id.text.toString().trim()
|
vnext.users[0].id = et_id.text.toString().trim()
|
||||||
if (config.configType == EConfigType.VMESS) {
|
if (config.configType == EConfigType.VMESS) {
|
||||||
vnext.users[0].alterId = Utils.parseInt(et_alterId.text.toString())
|
vnext.users[0].alterId = Utils.parseInt(et_alterId?.text.toString())
|
||||||
vnext.users[0].security = securitys[sp_security.selectedItemPosition]
|
vnext.users[0].security = securitys[sp_security?.selectedItemPosition ?: 0]
|
||||||
} else if (config.configType == EConfigType.VLESS) {
|
} else if (config.configType == EConfigType.VLESS) {
|
||||||
vnext.users[0].encryption = et_security.text.toString().trim()
|
vnext.users[0].encryption = et_security?.text.toString().trim()
|
||||||
if (streamSecuritys[sp_stream_security.selectedItemPosition] == XTLS) {
|
if (streamSecuritys[sp_stream_security?.selectedItemPosition ?: 0] == XTLS) {
|
||||||
// vnext.users[0].flow = if (flows[sp_flow.selectedItemPosition].isBlank()) V2rayConfig.DEFAULT_FLOW
|
vnext.users[0].flow = flows[sp_flow?.selectedItemPosition ?: 0].ifBlank { DEFAULT_FLOW }
|
||||||
// else flows[sp_flow.selectedItemPosition]
|
|
||||||
} else {
|
} else {
|
||||||
vnext.users[0].flow = ""
|
vnext.users[0].flow = ""
|
||||||
}
|
}
|
||||||
@@ -255,48 +281,56 @@ class ServerActivity : BaseActivity() {
|
|||||||
server.port = port
|
server.port = port
|
||||||
if (config.configType == EConfigType.SHADOWSOCKS) {
|
if (config.configType == EConfigType.SHADOWSOCKS) {
|
||||||
server.password = et_id.text.toString().trim()
|
server.password = et_id.text.toString().trim()
|
||||||
server.method = shadowsocksSecuritys[sp_security.selectedItemPosition]
|
server.method = shadowsocksSecuritys[sp_security?.selectedItemPosition ?: 0]
|
||||||
} else if (config.configType == EConfigType.SOCKS) {
|
} else if (config.configType == EConfigType.SOCKS) {
|
||||||
if (TextUtils.isEmpty(et_security.text) && TextUtils.isEmpty(et_id.text)) {
|
if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) {
|
||||||
server.users = null
|
server.users = null
|
||||||
} else {
|
} else {
|
||||||
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
|
||||||
socksUsersBean.user = et_security.text.toString().trim()
|
socksUsersBean.user = et_security?.text.toString().trim()
|
||||||
socksUsersBean.pass = et_id.text.toString().trim()
|
socksUsersBean.pass = et_id.text.toString().trim()
|
||||||
server.users = listOf(socksUsersBean)
|
server.users = listOf(socksUsersBean)
|
||||||
}
|
}
|
||||||
} else if (config.configType == EConfigType.TROJAN) {
|
} else if (config.configType == EConfigType.TROJAN) {
|
||||||
server.password = et_id.text.toString().trim()
|
server.password = et_id.text.toString().trim()
|
||||||
|
server.flow =
|
||||||
|
if (streamSecuritys[sp_stream_security?.selectedItemPosition ?: 0] == XTLS) {
|
||||||
|
flows[sp_flow?.selectedItemPosition ?: 0].ifBlank { DEFAULT_FLOW }
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean, config: ServerConfig) {
|
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
|
||||||
val network = if (sp_network != null) networks[sp_network.selectedItemPosition] else DEFAULT_NETWORK
|
val network = sp_network?.selectedItemPosition ?: return
|
||||||
val type = if (sp_header_type != null) transportTypes(network)[sp_header_type.selectedItemPosition] else "";
|
val type = sp_header_type?.selectedItemPosition ?: return
|
||||||
val requestHost = if (et_request_host != null) et_request_host.text.toString().trim() else ""
|
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
|
||||||
val path = if (et_path != null) et_path.text.toString().trim() else ""
|
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(
|
var sni = streamSetting.populateTransportSettings(
|
||||||
transport = network,
|
transport = networks[network],
|
||||||
headerType = type,
|
headerType = transportTypes(networks[network])[type],
|
||||||
host = requestHost,
|
host = requestHost,
|
||||||
path = path,
|
path = path,
|
||||||
seed = path,
|
seed = path,
|
||||||
quicSecurity = requestHost,
|
quicSecurity = requestHost,
|
||||||
key = path,
|
key = path,
|
||||||
mode = type,
|
mode = transportTypes(networks[network])[type],
|
||||||
serviceName = path
|
serviceName = path
|
||||||
)
|
)
|
||||||
val allowInsecure = if (sp_allow_insecure == null || allowinsecures[sp_allow_insecure.selectedItemPosition].isBlank()) {
|
if (sniField.isNotBlank()) {
|
||||||
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
|
sni = sniField
|
||||||
} else {
|
|
||||||
allowinsecures[sp_allow_insecure.selectedItemPosition].toBoolean()
|
|
||||||
}
|
}
|
||||||
val defaultTls = if (config.configType == EConfigType.TROJAN) V2rayConfig.TLS else ""
|
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {
|
||||||
streamSetting.populateTlsSettings(
|
settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
|
||||||
if (sp_stream_security != null) streamSecuritys[sp_stream_security.selectedItemPosition] else defaultTls,
|
} else {
|
||||||
allowInsecure,
|
allowinsecures[allowInsecureField].toBoolean()
|
||||||
sni
|
}
|
||||||
)
|
streamSetting.populateTlsSettings(streamSecuritys[streamSecurity], allowInsecure, sni)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transportTypes(network: String?): Array<out String> {
|
private fun transportTypes(network: String?): Array<out String> {
|
||||||
@@ -316,20 +350,27 @@ class ServerActivity : BaseActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun deleteServer(): Boolean {
|
private fun deleteServer(): Boolean {
|
||||||
if (editGuid.isNotEmpty()) {
|
if (editGuid.isNotEmpty()) {
|
||||||
|
if (editGuid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
|
||||||
|
if (settingsStorage?.decodeBool(AppConfig.PREF_CONFIRM_REMOVE) == true) {
|
||||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
MmkvManager.removeServer(editGuid)
|
MmkvManager.removeServer(editGuid)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
|
} else {
|
||||||
|
MmkvManager.removeServer(editGuid)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_server, menu)
|
menuInflater.inflate(R.menu.action_server, menu)
|
||||||
val delButton = menu?.findItem(R.id.del_config)
|
val delButton = menu.findItem(R.id.del_config)
|
||||||
val saveButton = menu?.findItem(R.id.save_config)
|
val saveButton = menu.findItem(R.id.save_config)
|
||||||
|
|
||||||
if (editGuid.isNotEmpty()) {
|
if (editGuid.isNotEmpty()) {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import android.view.MenuItem
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.blacksquircle.ui.language.json.JsonLanguage
|
import com.blacksquircle.ui.language.json.JsonLanguage
|
||||||
import com.google.gson.Gson
|
import com.google.gson.*
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
|
||||||
@@ -113,10 +113,10 @@ class ServerCustomConfigActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_server, menu)
|
menuInflater.inflate(R.menu.action_server, menu)
|
||||||
val delButton = menu?.findItem(R.id.del_config)
|
val delButton = menu.findItem(R.id.del_config)
|
||||||
val saveButton = menu?.findItem(R.id.save_config)
|
val saveButton = menu.findItem(R.id.save_config)
|
||||||
|
|
||||||
if (editGuid.isNotEmpty()) {
|
if (editGuid.isNotEmpty()) {
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package com.v2ray.ang.ui
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.preference.*
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import com.v2ray.ang.R
|
import androidx.preference.*
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.service.V2RayServiceManager
|
|
||||||
import com.v2ray.ang.util.Utils
|
import com.v2ray.ang.util.Utils
|
||||||
import com.v2ray.ang.viewmodel.SettingsViewModel
|
import com.v2ray.ang.viewmodel.SettingsViewModel
|
||||||
|
|
||||||
@@ -28,146 +26,31 @@ class SettingsActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SettingsFragment : PreferenceFragmentCompat() {
|
class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
|
private val perAppProxy by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_PER_APP_PROXY) }
|
||||||
private val localDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
private val localDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_LOCAL_DNS_ENABLED) }
|
||||||
private val fakeDns by lazy { findPreference(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
private val fakeDns by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_FAKE_DNS_ENABLED) }
|
||||||
private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) }
|
private val localDnsPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_LOCAL_DNS_PORT) }
|
||||||
private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) }
|
private val vpnDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_VPN_DNS) }
|
||||||
private val sppedEnabled by lazy { findPreference(AppConfig.PREF_SPEED_ENABLED) as CheckBoxPreference }
|
|
||||||
private val sniffingEnabled by lazy { findPreference(AppConfig.PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
|
||||||
private val proxySharing by lazy { findPreference(AppConfig.PREF_PROXY_SHARING) as CheckBoxPreference }
|
|
||||||
private val domainStrategy by lazy { findPreference(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
|
||||||
private val routingMode by lazy { findPreference(AppConfig.PREF_ROUTING_MODE) as ListPreference }
|
|
||||||
|
|
||||||
private val forwardIpv6 by lazy { findPreference(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
|
|
||||||
private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
|
||||||
private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
|
|
||||||
private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
|
|
||||||
|
|
||||||
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
||||||
|
private val remoteDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_REMOTE_DNS) }
|
||||||
|
private val domesticDns by lazy { findPreference<EditTextPreference>(AppConfig.PREF_DOMESTIC_DNS) }
|
||||||
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
|
private val socksPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_SOCKS_PORT) }
|
||||||
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
|
private val httpPort by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HTTP_PORT) }
|
||||||
|
private val routingCustom by lazy { findPreference<Preference>(AppConfig.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 licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
||||||
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
||||||
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
|
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
|
||||||
|
|
||||||
private val mode by lazy { findPreference(AppConfig.PREF_MODE) as ListPreference }
|
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
||||||
|
|
||||||
private fun restartProxy() {
|
|
||||||
Utils.stopVService(requireContext())
|
|
||||||
V2RayServiceManager.startV2Ray(requireContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isRunning(): Boolean {
|
|
||||||
return false //TODO no point of adding logic now since Settings will be changed soon
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
||||||
addPreferencesFromResource(R.xml.pref_settings)
|
addPreferencesFromResource(R.xml.pref_settings)
|
||||||
|
|
||||||
perAppProxy.setOnPreferenceClickListener {
|
routingCustom?.setOnPreferenceClickListener {
|
||||||
if (isRunning()) {
|
|
||||||
Utils.stopVService(requireContext())
|
|
||||||
}
|
|
||||||
startActivity(Intent(activity, PerAppProxyActivity::class.java))
|
|
||||||
perAppProxy.isChecked = true
|
|
||||||
true
|
|
||||||
}
|
|
||||||
sppedEnabled.setOnPreferenceClickListener {
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
sniffingEnabled.setOnPreferenceClickListener {
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
proxySharing.setOnPreferenceClickListener {
|
|
||||||
if (proxySharing.isChecked)
|
|
||||||
activity?.toast(R.string.toast_warning_pref_proxysharing)
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
domainStrategy.setOnPreferenceChangeListener { _, _ ->
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
routingMode.setOnPreferenceChangeListener { _, _ ->
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
routingCustom.setOnPreferenceClickListener {
|
|
||||||
if (isRunning())
|
|
||||||
Utils.stopVService(requireContext())
|
|
||||||
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
|
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardIpv6.setOnPreferenceClickListener {
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
enableLocalDns.setOnPreferenceClickListener {
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
domesticDns.setOnPreferenceChangeListener { _, any ->
|
|
||||||
// domesticDns.summary = any as String
|
|
||||||
val nval = any as String
|
|
||||||
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteDns.setOnPreferenceChangeListener { _, any ->
|
|
||||||
// remoteDns.summary = any as String
|
|
||||||
val nval = any as String
|
|
||||||
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
|
|
||||||
if (isRunning())
|
|
||||||
restartProxy()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
localDns?.setOnPreferenceChangeListener{ _, any ->
|
|
||||||
updateLocalDns(any as Boolean)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
localDnsPort?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
val nval = any as String
|
|
||||||
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) "10807" else nval
|
|
||||||
true
|
|
||||||
}
|
|
||||||
vpnDns?.setOnPreferenceChangeListener { _, any ->
|
|
||||||
vpnDns?.summary = any as String
|
|
||||||
true
|
|
||||||
}
|
|
||||||
mode.setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
updateMode(newValue.toString())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
mode.dialogLayoutResource = R.layout.preference_with_help_link
|
|
||||||
|
|
||||||
// donate.onClick {
|
|
||||||
// startActivity<InappBuyActivity>()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// licenses.onClick {
|
// licenses.onClick {
|
||||||
// val fragment = LicensesDialogFragment.Builder(act)
|
// val fragment = LicensesDialogFragment.Builder(act)
|
||||||
// .setNotices(R.raw.licenses)
|
// .setNotices(R.raw.licenses)
|
||||||
@@ -190,42 +73,92 @@ class SettingsActivity : BaseActivity() {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
perAppProxy?.setOnPreferenceClickListener {
|
||||||
|
startActivity(Intent(activity, PerAppProxyActivity::class.java))
|
||||||
|
perAppProxy?.isChecked = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
// socksPort.setOnPreferenceChangeListener { preference, any ->
|
remoteDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
// socksPort.summary = any as String
|
// remoteDns.summary = any as String
|
||||||
// true
|
val nval = any as String
|
||||||
// }
|
remoteDns?.summary = if (nval == "") AppConfig.DNS_AGENT else nval
|
||||||
// httpPort.setOnPreferenceChangeListener { preference, any ->
|
true
|
||||||
// httpPort.summary = any as String
|
}
|
||||||
// true
|
domesticDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
// }
|
// domesticDns.summary = any as String
|
||||||
|
val nval = any as String
|
||||||
|
domesticDns?.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
localDns?.setOnPreferenceChangeListener{ _, any ->
|
||||||
|
updateLocalDns(any as Boolean)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
localDnsPort?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_LOCAL_DNS else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
vpnDns?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
vpnDns?.summary = any as String
|
||||||
|
true
|
||||||
|
}
|
||||||
|
socksPort?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
socksPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_SOCKS else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
httpPort?.setOnPreferenceChangeListener { _, any ->
|
||||||
|
val nval = any as String
|
||||||
|
httpPort?.summary = if (TextUtils.isEmpty(nval)) AppConfig.PORT_HTTP else nval
|
||||||
|
true
|
||||||
|
}
|
||||||
|
mode?.setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
updateMode(newValue.toString())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
mode?.dialogLayoutResource = R.layout.preference_with_help_link
|
||||||
|
//loglevel.summary = "LogLevel"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||||
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
|
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
|
||||||
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
|
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
|
||||||
domesticDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
|
domesticDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
|
||||||
|
|
||||||
|
localDnsPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_LOCAL_DNS_PORT, AppConfig.PORT_LOCAL_DNS)
|
||||||
|
socksPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_SOCKS_PORT, AppConfig.PORT_SOCKS)
|
||||||
|
httpPort?.summary = defaultSharedPreferences.getString(AppConfig.PREF_HTTP_PORT, AppConfig.PORT_HTTP)
|
||||||
|
|
||||||
if (TextUtils.isEmpty(remoteDnsString)) {
|
if (TextUtils.isEmpty(remoteDnsString)) {
|
||||||
remoteDnsString = AppConfig.DNS_AGENT
|
remoteDnsString = AppConfig.DNS_AGENT
|
||||||
}
|
}
|
||||||
if ( domesticDns.summary == "") {
|
if (TextUtils.isEmpty(domesticDns?.summary)) {
|
||||||
domesticDns.summary = AppConfig.DNS_DIRECT
|
domesticDns?.summary = AppConfig.DNS_DIRECT
|
||||||
}
|
}
|
||||||
remoteDns.summary = remoteDnsString
|
remoteDns?.summary = remoteDnsString
|
||||||
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
|
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
|
||||||
|
|
||||||
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
|
if (TextUtils.isEmpty(localDnsPort?.summary)) {
|
||||||
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
|
localDnsPort?.summary = AppConfig.PORT_LOCAL_DNS
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(socksPort?.summary)) {
|
||||||
|
socksPort?.summary = AppConfig.PORT_SOCKS
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(httpPort?.summary)) {
|
||||||
|
httpPort?.summary = AppConfig.PORT_HTTP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMode(mode: String?) {
|
private fun updateMode(mode: String?) {
|
||||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||||
val vpn = mode == "VPN"
|
val vpn = mode == "VPN"
|
||||||
perAppProxy.isEnabled = vpn
|
perAppProxy?.isEnabled = vpn
|
||||||
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
|
perAppProxy?.isChecked = PreferenceManager.getDefaultSharedPreferences(requireActivity())
|
||||||
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
|
||||||
localDns?.isEnabled = vpn
|
localDns?.isEnabled = vpn
|
||||||
fakeDns?.isEnabled = vpn
|
fakeDns?.isEnabled = vpn
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
@@ -45,7 +45,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
private fun bindingServer(subItem: SubscriptionItem): Boolean {
|
private fun bindingServer(subItem: SubscriptionItem): Boolean {
|
||||||
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
|
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
|
||||||
binding.etUrl.text = Utils.getEditable(subItem.url)
|
binding.etUrl.text = Utils.getEditable(subItem.url)
|
||||||
|
binding.chkEnable.isChecked = subItem.enabled
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class SubEditActivity : BaseActivity() {
|
|||||||
private fun clearServer(): Boolean {
|
private fun clearServer(): Boolean {
|
||||||
binding.etRemarks.text = null
|
binding.etRemarks.text = null
|
||||||
binding.etUrl.text = null
|
binding.etUrl.text = null
|
||||||
|
binding.chkEnable.isChecked = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,15 +75,16 @@ class SubEditActivity : BaseActivity() {
|
|||||||
|
|
||||||
subItem.remarks = binding.etRemarks.text.toString()
|
subItem.remarks = binding.etRemarks.text.toString()
|
||||||
subItem.url = binding.etUrl.text.toString()
|
subItem.url = binding.etUrl.text.toString()
|
||||||
|
subItem.enabled = binding.chkEnable.isChecked
|
||||||
|
|
||||||
if (TextUtils.isEmpty(subItem.remarks)) {
|
if (TextUtils.isEmpty(subItem.remarks)) {
|
||||||
toast(R.string.sub_setting_remarks)
|
toast(R.string.sub_setting_remarks)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (TextUtils.isEmpty(subItem.url)) {
|
// if (TextUtils.isEmpty(subItem.url)) {
|
||||||
toast(R.string.sub_setting_url)
|
// toast(R.string.sub_setting_url)
|
||||||
return false
|
// return false
|
||||||
}
|
// }
|
||||||
|
|
||||||
subStorage?.encode(subId, Gson().toJson(subItem))
|
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||||
toast(R.string.toast_success)
|
toast(R.string.toast_success)
|
||||||
@@ -106,10 +107,10 @@ class SubEditActivity : BaseActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_server, menu)
|
menuInflater.inflate(R.menu.action_server, menu)
|
||||||
del_config = menu?.findItem(R.id.del_config)
|
del_config = menu.findItem(R.id.del_config)
|
||||||
save_config = menu?.findItem(R.id.save_config)
|
save_config = menu.findItem(R.id.save_config)
|
||||||
|
|
||||||
if (editSubId.isEmpty()) {
|
if (editSubId.isEmpty()) {
|
||||||
del_config?.isVisible = false
|
del_config?.isVisible = false
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package com.v2ray.ang.ui
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||||
import com.v2ray.ang.dto.SubscriptionItem
|
import com.v2ray.ang.dto.SubscriptionItem
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
@@ -37,10 +37,10 @@ class SubSettingActivity : BaseActivity() {
|
|||||||
adapter.notifyDataSetChanged()
|
adapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
menuInflater.inflate(R.menu.action_sub_setting, menu)
|
||||||
menu?.findItem(R.id.del_config)?.isVisible = false
|
menu.findItem(R.id.del_config)?.isVisible = false
|
||||||
menu?.findItem(R.id.save_config)?.isVisible = false
|
menu.findItem(R.id.save_config)?.isVisible = false
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,19 @@ package com.v2ray.ang.ui
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
import com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
|
||||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
|
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
|
||||||
|
|
||||||
private var mActivity: SubSettingActivity = activity
|
private var mActivity: SubSettingActivity = activity
|
||||||
|
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
override fun getItemCount() = mActivity.subscriptions.size
|
override fun getItemCount() = mActivity.subscriptions.size
|
||||||
|
|
||||||
@@ -18,6 +23,11 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
|||||||
val subItem = mActivity.subscriptions[position].second
|
val subItem = mActivity.subscriptions[position].second
|
||||||
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
holder.itemSubSettingBinding.tvName.text = subItem.remarks
|
||||||
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
holder.itemSubSettingBinding.tvUrl.text = subItem.url
|
||||||
|
if (subItem.enabled) {
|
||||||
|
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorSelected)
|
||||||
|
} else {
|
||||||
|
holder.itemSubSettingBinding.chkEnable.setBackgroundResource(R.color.colorUnselected)
|
||||||
|
}
|
||||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||||
|
|
||||||
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
|
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
|
||||||
@@ -25,6 +35,11 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
|||||||
.putExtra("subId", subId)
|
.putExtra("subId", subId)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
holder.itemSubSettingBinding.infoContainer.setOnClickListener {
|
||||||
|
subItem.enabled = !subItem.enabled
|
||||||
|
subStorage?.encode(subId, Gson().toJson(subItem))
|
||||||
|
notifyItemChanged(position)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
|
||||||
|
|||||||
@@ -95,9 +95,9 @@ class TaskerActivity : BaseActivity() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.action_server, menu)
|
menuInflater.inflate(R.menu.action_server, menu)
|
||||||
val del_config = menu?.findItem(R.id.del_config)
|
val del_config = menu.findItem(R.id.del_config)
|
||||||
del_config?.isVisible = false
|
del_config?.isVisible = false
|
||||||
return super.onCreateOptionsMenu(menu)
|
return super.onCreateOptionsMenu(menu)
|
||||||
}
|
}
|
||||||
@@ -114,3 +114,4 @@ class TaskerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.google.zxing.WriterException
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.databinding.ActivityLogcatBinding
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.AngConfigManager
|
||||||
|
|
||||||
|
class UrlSchemeActivity : BaseActivity() {
|
||||||
|
private lateinit var binding: ActivityLogcatBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityLogcatBinding.inflate(layoutInflater)
|
||||||
|
val view = binding.root
|
||||||
|
setContentView(view)
|
||||||
|
|
||||||
|
var shareUrl: String = ""
|
||||||
|
try {
|
||||||
|
intent?.apply {
|
||||||
|
when (action) {
|
||||||
|
Intent.ACTION_SEND -> {
|
||||||
|
if ("text/plain" == type) {
|
||||||
|
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
||||||
|
shareUrl = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Intent.ACTION_VIEW -> {
|
||||||
|
val uri: Uri? = intent.data
|
||||||
|
shareUrl = uri?.getQueryParameter("url")!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toast(shareUrl)
|
||||||
|
val count = AngConfigManager.importBatchConfig(shareUrl, "", false)
|
||||||
|
if (count > 0) {
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
} else {
|
||||||
|
toast(R.string.toast_failure)
|
||||||
|
}
|
||||||
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
|
finish()
|
||||||
|
} catch (e: WriterException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package com.v2ray.ang.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.*
|
||||||
|
import android.view.View.GONE
|
||||||
|
import android.view.View.VISIBLE
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.tbruyelle.rxpermissions.RxPermissions
|
||||||
|
import com.tencent.mmkv.MMKV
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.databinding.ActivitySubSettingBinding
|
||||||
|
import com.v2ray.ang.databinding.ItemRecyclerUserAssetBinding
|
||||||
|
import com.v2ray.ang.extension.toTrafficString
|
||||||
|
import com.v2ray.ang.extension.toast
|
||||||
|
import com.v2ray.ang.util.MmkvManager
|
||||||
|
import com.v2ray.ang.util.Utils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.net.HttpURLConnection
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
import java.net.URL
|
||||||
|
import java.text.DateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class UserAssetActivity : BaseActivity() {
|
||||||
|
private lateinit var binding: ActivitySubSettingBinding
|
||||||
|
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
|
val extDir by lazy { File(Utils.userAssetPath(this)) }
|
||||||
|
val geofiles = arrayOf("geosite.dat", "geoip.dat")
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivitySubSettingBinding.inflate(layoutInflater)
|
||||||
|
val view = binding.root
|
||||||
|
setContentView(view)
|
||||||
|
title = getString(R.string.title_user_asset_setting)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
|
binding.recyclerView.layoutManager = LinearLayoutManager(this)
|
||||||
|
binding.recyclerView.adapter = UserAssetAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.menu_asset, menu)
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
|
R.id.add_file -> {
|
||||||
|
showFileChooser()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.download_file -> {
|
||||||
|
downloadGeoFiles()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showFileChooser() {
|
||||||
|
RxPermissions(this).request(Manifest.permission.READ_EXTERNAL_STORAGE).subscribe {
|
||||||
|
if (it) {
|
||||||
|
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||||
|
intent.type = "*/*"
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
|
||||||
|
try {
|
||||||
|
chooseFile.launch(
|
||||||
|
Intent.createChooser(
|
||||||
|
intent,
|
||||||
|
getString(R.string.title_file_chooser)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch (ex: android.content.ActivityNotFoundException) {
|
||||||
|
toast(R.string.toast_require_file_manager)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val chooseFile =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
val uri = it.data?.data
|
||||||
|
if (it.resultCode == RESULT_OK && uri != null) {
|
||||||
|
try {
|
||||||
|
copyFile(uri)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast(R.string.toast_asset_copy_failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyFile(uri: Uri): String {
|
||||||
|
val targetFile = File(extDir, getCursorName(uri) ?: uri.toString())
|
||||||
|
contentResolver.openInputStream(uri).use { inputStream ->
|
||||||
|
targetFile.outputStream().use { fileOut ->
|
||||||
|
inputStream?.copyTo(fileOut)
|
||||||
|
toast(R.string.toast_success)
|
||||||
|
binding.recyclerView.adapter?.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return targetFile.path
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCursorName(uri: Uri): String? = try {
|
||||||
|
contentResolver.query(uri, null, null, null, null)?.let { cursor ->
|
||||||
|
cursor.run {
|
||||||
|
if (moveToFirst()) getString(getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
||||||
|
else null
|
||||||
|
}.also { cursor.close() }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadGeoFiles() {
|
||||||
|
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||||
|
|
||||||
|
toast(R.string.msg_downloading_content)
|
||||||
|
geofiles.forEach {
|
||||||
|
//toast(getString(R.string.msg_downloading_content) + it)
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val result = downloadGeo(it, 60000, httpPort)
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
if (result) {
|
||||||
|
toast(getString(R.string.toast_success) + " " + it)
|
||||||
|
binding.recyclerView.adapter?.notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
toast(getString(R.string.toast_failure) + " " + it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadGeo(name: String, timeout: Int, httpPort: Int): Boolean {
|
||||||
|
val url = AppConfig.geoUrl + name
|
||||||
|
val targetTemp = File(extDir, name + "_temp")
|
||||||
|
val target = File(extDir, name)
|
||||||
|
var conn: HttpURLConnection? = null
|
||||||
|
//Log.d(AppConfig.ANG_PACKAGE, url)
|
||||||
|
|
||||||
|
try {
|
||||||
|
conn = URL(url).openConnection(
|
||||||
|
Proxy(
|
||||||
|
Proxy.Type.HTTP,
|
||||||
|
InetSocketAddress("127.0.0.1", httpPort)
|
||||||
|
)
|
||||||
|
) as HttpURLConnection
|
||||||
|
conn.connectTimeout = timeout
|
||||||
|
conn.readTimeout = timeout
|
||||||
|
val inputStream = conn.inputStream
|
||||||
|
val responseCode = conn.responseCode
|
||||||
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
|
FileOutputStream(targetTemp).use { output ->
|
||||||
|
inputStream.copyTo(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetTemp.renameTo(target)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(AppConfig.ANG_PACKAGE, Log.getStackTraceString(e))
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
conn?.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class UserAssetAdapter : RecyclerView.Adapter<UserAssetViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserAssetViewHolder {
|
||||||
|
return UserAssetViewHolder(ItemRecyclerUserAssetBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
override fun onBindViewHolder(holder: UserAssetViewHolder, position: Int) {
|
||||||
|
val file = extDir.listFiles()?.getOrNull(position) ?: return
|
||||||
|
holder.itemUserAssetBinding.assetName.text = file.name
|
||||||
|
val dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM)
|
||||||
|
holder.itemUserAssetBinding.assetProperties.text = "${file.length().toTrafficString()} • ${dateFormat.format(Date(file.lastModified()))}"
|
||||||
|
if (file.name in geofiles) {
|
||||||
|
holder.itemUserAssetBinding.layoutRemove.visibility = GONE
|
||||||
|
} else {
|
||||||
|
holder.itemUserAssetBinding.layoutRemove.visibility = VISIBLE
|
||||||
|
}
|
||||||
|
holder.itemUserAssetBinding.layoutRemove.setOnClickListener {
|
||||||
|
file.delete()
|
||||||
|
binding.recyclerView.adapter?.notifyItemRemoved(position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return extDir.listFiles()?.size ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserAssetViewHolder(val itemUserAssetBinding: ItemRecyclerUserAssetBinding) : RecyclerView.ViewHolder(itemUserAssetBinding.root)
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@ package com.v2ray.ang.util
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
@@ -17,8 +18,8 @@ import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_SECURITY
|
|||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
import com.v2ray.ang.dto.V2rayConfig.Companion.TLS
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import com.v2ray.ang.extension.idnHost
|
||||||
|
|
||||||
object AngConfigManager {
|
object AngConfigManager {
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
||||||
@@ -54,27 +55,31 @@ object AngConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun copyLegacySettings(sharedPreferences: SharedPreferences) {
|
private fun copyLegacySettings(sharedPreferences: SharedPreferences) {
|
||||||
listOf(AppConfig.PREF_MODE,
|
listOf(
|
||||||
|
AppConfig.PREF_MODE,
|
||||||
AppConfig.PREF_REMOTE_DNS,
|
AppConfig.PREF_REMOTE_DNS,
|
||||||
AppConfig.PREF_DOMESTIC_DNS,
|
AppConfig.PREF_DOMESTIC_DNS,
|
||||||
// AppConfig.PREF_LOCAL_DNS_PORT,
|
AppConfig.PREF_LOCAL_DNS_PORT,
|
||||||
// AppConfig.PREF_SOCKS_PORT,
|
AppConfig.PREF_SOCKS_PORT,
|
||||||
// AppConfig.PREF_HTTP_PORT,
|
AppConfig.PREF_HTTP_PORT,
|
||||||
// AppConfig.PREF_LOGLEVEL,
|
AppConfig.PREF_LOGLEVEL,
|
||||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||||
AppConfig.PREF_ROUTING_MODE,
|
AppConfig.PREF_ROUTING_MODE,
|
||||||
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
||||||
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
|
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
|
||||||
AppConfig.PREF_V2RAY_ROUTING_DIRECT,).forEach { key ->
|
AppConfig.PREF_V2RAY_ROUTING_DIRECT,
|
||||||
|
).forEach { key ->
|
||||||
settingsStorage?.encode(key, sharedPreferences.getString(key, null))
|
settingsStorage?.encode(key, sharedPreferences.getString(key, null))
|
||||||
}
|
}
|
||||||
listOf(AppConfig.PREF_SPEED_ENABLED,
|
listOf(
|
||||||
|
AppConfig.PREF_SPEED_ENABLED,
|
||||||
AppConfig.PREF_PROXY_SHARING,
|
AppConfig.PREF_PROXY_SHARING,
|
||||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||||
// AppConfig.PREF_ALLOW_INSECURE,
|
AppConfig.PREF_ALLOW_INSECURE,
|
||||||
// AppConfig.PREF_PREFER_IPV6,
|
AppConfig.PREF_PREFER_IPV6,
|
||||||
AppConfig.PREF_PER_APP_PROXY,
|
AppConfig.PREF_PER_APP_PROXY,
|
||||||
AppConfig.PREF_BYPASS_APPS,).forEach { key ->
|
AppConfig.PREF_BYPASS_APPS,
|
||||||
|
).forEach { key ->
|
||||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||||
}
|
}
|
||||||
settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true))
|
settingsStorage?.encode(AppConfig.PREF_SNIFFING_ENABLED, sharedPreferences.getBoolean(AppConfig.PREF_SNIFFING_ENABLED, true))
|
||||||
@@ -107,7 +112,7 @@ object AngConfigManager {
|
|||||||
vnext.users[0].security = vmessBean.security
|
vnext.users[0].security = vmessBean.security
|
||||||
} else if (config.configType == EConfigType.VLESS) {
|
} else if (config.configType == EConfigType.VLESS) {
|
||||||
vnext.users[0].encryption = vmessBean.security
|
vnext.users[0].encryption = vmessBean.security
|
||||||
// vnext.users[0].flow = vmessBean.flow
|
vnext.users[0].flow = vmessBean.flow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
@@ -133,13 +138,13 @@ object AngConfigManager {
|
|||||||
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType,
|
val sni = streamSetting.populateTransportSettings(vmessBean.network, vmessBean.headerType,
|
||||||
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path,
|
vmessBean.requestHost, vmessBean.path, vmessBean.path, vmessBean.requestHost, vmessBean.path,
|
||||||
vmessBean.headerType, vmessBean.path)
|
vmessBean.headerType, vmessBean.path)
|
||||||
// val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
|
val allowInsecure = if (vmessBean.allowInsecure.isBlank()) {
|
||||||
// settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
// } else {
|
} else {
|
||||||
// vmessBean.allowInsecure.toBoolean()
|
vmessBean.allowInsecure.toBoolean()
|
||||||
// }
|
}
|
||||||
streamSetting.populateTlsSettings(vmessBean.streamSecurity, false,
|
streamSetting.populateTlsSettings(vmessBean.streamSecurity, allowInsecure,
|
||||||
sni)//if (vmessBean.sni.isNotBlank()) vmessBean.sni else sni)
|
vmessBean.sni.ifBlank { sni })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
|
val key = MmkvManager.encodeServerConfig(vmessBean.guid, config)
|
||||||
@@ -154,7 +159,7 @@ object AngConfigManager {
|
|||||||
val subItem = SubscriptionItem()
|
val subItem = SubscriptionItem()
|
||||||
subItem.remarks = it.remarks
|
subItem.remarks = it.remarks
|
||||||
subItem.url = it.url
|
subItem.url = it.url
|
||||||
//subItem.enabled = it.enabled
|
subItem.enabled = it.enabled
|
||||||
subStorage?.encode(it.id, Gson().toJson(subItem))
|
subStorage?.encode(it.id, Gson().toJson(subItem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,13 +174,13 @@ object AngConfigManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//maybe sub
|
//maybe sub
|
||||||
if (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL)) {
|
if (TextUtils.isEmpty(subid) && (str.startsWith(HTTP_PROTOCOL) || str.startsWith(HTTPS_PROTOCOL))) {
|
||||||
MmkvManager.importUrlAsSubscription(str)
|
MmkvManager.importUrlAsSubscription(str)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var config: ServerConfig? = null
|
var config: ServerConfig? = null
|
||||||
val allowInsecure = false//settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
val allowInsecure = settingsStorage?.decodeBool(AppConfig.PREF_ALLOW_INSECURE) ?: false
|
||||||
if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
if (str.startsWith(EConfigType.VMESS.protocolScheme)) {
|
||||||
config = ServerConfig.create(EConfigType.VMESS)
|
config = ServerConfig.create(EConfigType.VMESS)
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
||||||
@@ -196,7 +201,6 @@ object AngConfigManager {
|
|||||||
if (TextUtils.isEmpty(vmessQRCode.add)
|
if (TextUtils.isEmpty(vmessQRCode.add)
|
||||||
|| TextUtils.isEmpty(vmessQRCode.port)
|
|| TextUtils.isEmpty(vmessQRCode.port)
|
||||||
|| TextUtils.isEmpty(vmessQRCode.id)
|
|| TextUtils.isEmpty(vmessQRCode.id)
|
||||||
|| TextUtils.isEmpty(vmessQRCode.aid)
|
|
||||||
|| TextUtils.isEmpty(vmessQRCode.net)
|
|| TextUtils.isEmpty(vmessQRCode.net)
|
||||||
) {
|
) {
|
||||||
return R.string.toast_incorrect_protocol
|
return R.string.toast_incorrect_protocol
|
||||||
@@ -207,7 +211,7 @@ object AngConfigManager {
|
|||||||
vnext.address = vmessQRCode.add
|
vnext.address = vmessQRCode.add
|
||||||
vnext.port = Utils.parseInt(vmessQRCode.port)
|
vnext.port = Utils.parseInt(vmessQRCode.port)
|
||||||
vnext.users[0].id = vmessQRCode.id
|
vnext.users[0].id = vmessQRCode.id
|
||||||
vnext.users[0].encryption = DEFAULT_SECURITY
|
vnext.users[0].security = if (TextUtils.isEmpty(vmessQRCode.scy)) DEFAULT_SECURITY else vmessQRCode.scy
|
||||||
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
vnext.users[0].alterId = Utils.parseInt(vmessQRCode.aid)
|
||||||
}
|
}
|
||||||
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host,
|
val sni = streamSetting.populateTransportSettings(vmessQRCode.net, vmessQRCode.type, vmessQRCode.host,
|
||||||
@@ -217,9 +221,10 @@ object AngConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.SHADOWSOCKS.protocolScheme)) {
|
||||||
|
config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
||||||
|
if (!tryResolveResolveSip002(str, config)) {
|
||||||
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
var result = str.replace(EConfigType.SHADOWSOCKS.protocolScheme, "")
|
||||||
val indexSplit = result.indexOf("#")
|
val indexSplit = result.indexOf("#")
|
||||||
config = ServerConfig.create(EConfigType.SHADOWSOCKS)
|
|
||||||
if (indexSplit > 0) {
|
if (indexSplit > 0) {
|
||||||
try {
|
try {
|
||||||
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
config.remarks = Utils.urlDecode(result.substring(indexSplit + 1, result.length))
|
||||||
@@ -247,6 +252,7 @@ object AngConfigManager {
|
|||||||
server.password = match.groupValues[2]
|
server.password = match.groupValues[2]
|
||||||
server.method = match.groupValues[1].lowercase()
|
server.method = match.groupValues[1].lowercase()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.SOCKS.protocolScheme)) {
|
||||||
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
var result = str.replace(EConfigType.SOCKS.protocolScheme, "")
|
||||||
val indexSplit = result.indexOf("#")
|
val indexSplit = result.indexOf("#")
|
||||||
@@ -264,7 +270,7 @@ object AngConfigManager {
|
|||||||
//part decode
|
//part decode
|
||||||
val indexS = result.indexOf("@")
|
val indexS = result.indexOf("@")
|
||||||
if (indexS > 0) {
|
if (indexS > 0) {
|
||||||
//result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
|
result = Utils.decode(result.substring(0, indexS)) + result.substring(indexS, result.length)
|
||||||
} else {
|
} else {
|
||||||
result = Utils.decode(result)
|
result = Utils.decode(result)
|
||||||
}
|
}
|
||||||
@@ -281,32 +287,39 @@ object AngConfigManager {
|
|||||||
server.users = listOf(socksUsersBean)
|
server.users = listOf(socksUsersBean)
|
||||||
}
|
}
|
||||||
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.TROJAN.protocolScheme)) {
|
||||||
val uri = URI(str)
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
config = ServerConfig.create(EConfigType.TROJAN)
|
config = ServerConfig.create(EConfigType.TROJAN)
|
||||||
config.remarks = uri.fragment ?: ""
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
|
||||||
|
var flow = ""
|
||||||
|
if (uri.rawQuery != null) {
|
||||||
|
val queryParam = uri.rawQuery.split("&")
|
||||||
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
|
|
||||||
|
val sni = config.outboundBean?.streamSettings?.populateTransportSettings(queryParam["type"] ?: "tcp", queryParam["headerType"],
|
||||||
|
queryParam["host"], queryParam["path"], queryParam["seed"], queryParam["quicSecurity"], queryParam["key"],
|
||||||
|
queryParam["mode"], queryParam["serviceName"])
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(queryParam["security"] ?: TLS, allowInsecure, queryParam["sni"] ?: sni!!)
|
||||||
|
flow = queryParam["flow"] ?: ""
|
||||||
|
} else {
|
||||||
|
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, "")
|
||||||
|
}
|
||||||
|
|
||||||
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
server.address = uri.host
|
server.address = uri.idnHost
|
||||||
server.port = uri.port
|
server.port = uri.port
|
||||||
server.password = uri.userInfo
|
server.password = uri.userInfo
|
||||||
|
server.flow = flow
|
||||||
}
|
}
|
||||||
var sni = ""
|
|
||||||
uri.rawQuery?.let { rawQuery ->
|
|
||||||
val queryParam = rawQuery.split("&")
|
|
||||||
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
|
|
||||||
.toMap()
|
|
||||||
sni = queryParam["sni"] ?: ""
|
|
||||||
}
|
|
||||||
config.outboundBean?.streamSettings?.populateTlsSettings(TLS, allowInsecure, sni)
|
|
||||||
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
} else if (str.startsWith(EConfigType.VLESS.protocolScheme)) {
|
||||||
val uri = URI(str)
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
val queryParam = uri.rawQuery.split("&")
|
val queryParam = uri.rawQuery.split("&")
|
||||||
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
.toMap()
|
|
||||||
config = ServerConfig.create(EConfigType.VLESS)
|
config = ServerConfig.create(EConfigType.VLESS)
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
val streamSetting = config.outboundBean?.streamSettings ?: return -1
|
||||||
config.remarks = uri.fragment ?: ""
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
vnext.address = uri.host
|
vnext.address = uri.idnHost
|
||||||
vnext.port = uri.port
|
vnext.port = uri.port
|
||||||
vnext.users[0].id = uri.userInfo
|
vnext.users[0].id = uri.userInfo
|
||||||
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
vnext.users[0].encryption = queryParam["encryption"] ?: "none"
|
||||||
@@ -340,21 +353,20 @@ object AngConfigManager {
|
|||||||
val uri = URI(uriString)
|
val uri = URI(uriString)
|
||||||
check(uri.scheme == "vmess")
|
check(uri.scheme == "vmess")
|
||||||
val (_, protocol, tlsStr, uuid, alterId) =
|
val (_, protocol, tlsStr, uuid, alterId) =
|
||||||
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})-([0-9]+)")
|
Regex("(tcp|http|ws|kcp|quic|grpc)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})")
|
||||||
.matchEntire(uri.userInfo)?.groupValues
|
.matchEntire(uri.userInfo)?.groupValues
|
||||||
?: error("parse user info fail.")
|
?: error("parse user info fail.")
|
||||||
val tls = tlsStr.isNotBlank()
|
val tls = tlsStr.isNotBlank()
|
||||||
val queryParam = uri.rawQuery.split("&")
|
val queryParam = uri.rawQuery.split("&")
|
||||||
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
|
.associate { it.split("=").let { (k, v) -> k to Utils.urlDecode(v) } }
|
||||||
.toMap()
|
|
||||||
|
|
||||||
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
val streamSetting = config.outboundBean?.streamSettings ?: return false
|
||||||
config.remarks = uri.fragment
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
config.outboundBean.settings?.vnext?.get(0)?.let { vnext ->
|
||||||
vnext.address = uri.host
|
vnext.address = uri.idnHost
|
||||||
vnext.port = uri.port
|
vnext.port = uri.port
|
||||||
vnext.users[0].id = uuid
|
vnext.users[0].id = uuid
|
||||||
vnext.users[0].encryption = DEFAULT_SECURITY
|
vnext.users[0].security = DEFAULT_SECURITY
|
||||||
vnext.users[0].alterId = alterId.toInt()
|
vnext.users[0].alterId = alterId.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -391,12 +403,49 @@ object AngConfigManager {
|
|||||||
vnext.address = arr22[0]
|
vnext.address = arr22[0]
|
||||||
vnext.port = Utils.parseInt(arr22[1])
|
vnext.port = Utils.parseInt(arr22[1])
|
||||||
vnext.users[0].id = arr21[1]
|
vnext.users[0].id = arr21[1]
|
||||||
vnext.users[0].encryption = arr21[0]
|
vnext.users[0].security = arr21[0]
|
||||||
vnext.users[0].alterId = 0
|
vnext.users[0].alterId = 0
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryResolveResolveSip002(str: String, config: ServerConfig): Boolean {
|
||||||
|
try {
|
||||||
|
val uri = URI(Utils.fixIllegalUrl(str))
|
||||||
|
config.remarks = Utils.urlDecode(uri.fragment ?: "")
|
||||||
|
|
||||||
|
val method: String
|
||||||
|
val password: String
|
||||||
|
if (uri.userInfo.contains(":")) {
|
||||||
|
val arrUserInfo = uri.userInfo.split(":").map { it.trim() }
|
||||||
|
if (arrUserInfo.count() != 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
method = arrUserInfo[0]
|
||||||
|
password = Utils.urlDecode(arrUserInfo[1])
|
||||||
|
} else {
|
||||||
|
val base64Decode = Utils.decode(uri.userInfo)
|
||||||
|
val arrUserInfo = base64Decode.split(":").map { it.trim() }
|
||||||
|
if (arrUserInfo.count() < 2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
method = arrUserInfo[0]
|
||||||
|
password = base64Decode.substringAfter(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
|
||||||
|
server.address = uri.idnHost
|
||||||
|
server.port = uri.port
|
||||||
|
server.password = password
|
||||||
|
server.method = method
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, e.toString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* share config
|
* share config
|
||||||
*/
|
*/
|
||||||
@@ -414,6 +463,7 @@ object AngConfigManager {
|
|||||||
vmessQRCode.port = outbound.getServerPort().toString()
|
vmessQRCode.port = outbound.getServerPort().toString()
|
||||||
vmessQRCode.id = outbound.getPassword().orEmpty()
|
vmessQRCode.id = outbound.getPassword().orEmpty()
|
||||||
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
vmessQRCode.aid = outbound.settings?.vnext?.get(0)?.users?.get(0)?.alterId.toString()
|
||||||
|
vmessQRCode.scy = outbound.settings?.vnext?.get(0)?.users?.get(0)?.security.toString()
|
||||||
vmessQRCode.net = streamSetting.network
|
vmessQRCode.net = streamSetting.network
|
||||||
vmessQRCode.tls = streamSetting.security
|
vmessQRCode.tls = streamSetting.security
|
||||||
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
vmessQRCode.sni = streamSetting.tlsSettings?.serverName.orEmpty()
|
||||||
@@ -428,55 +478,62 @@ object AngConfigManager {
|
|||||||
EConfigType.CUSTOM -> ""
|
EConfigType.CUSTOM -> ""
|
||||||
EConfigType.SHADOWSOCKS -> {
|
EConfigType.SHADOWSOCKS -> {
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
val url = String.format("%s:%s@%s:%s",
|
val pw = Utils.encode("${outbound.getSecurityEncryption()}:${outbound.getPassword()}")
|
||||||
outbound.getSecurityEncryption(),
|
val url = String.format("%s@%s:%s",
|
||||||
outbound.getPassword(),
|
pw,
|
||||||
outbound.getServerAddress(),
|
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||||
outbound.getServerPort())
|
outbound.getServerPort())
|
||||||
Utils.encode(url) + remark
|
url + remark
|
||||||
}
|
}
|
||||||
EConfigType.SOCKS -> {
|
EConfigType.SOCKS -> {
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
val url = String.format("%s:%s@%s:%s",
|
val pw = Utils.encode("${outbound.settings?.servers?.get(0)?.users?.get(0)?.user}:${outbound.getPassword()}")
|
||||||
outbound.settings?.servers?.get(0)?.users?.get(0)?.user,
|
val url = String.format("%s@%s:%s",
|
||||||
outbound.getPassword(),
|
pw,
|
||||||
outbound.getServerAddress(),
|
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||||
outbound.getServerPort())
|
outbound.getServerPort())
|
||||||
Utils.encode(url) + remark
|
url + remark
|
||||||
}
|
}
|
||||||
EConfigType.VLESS -> {
|
EConfigType.VLESS,
|
||||||
|
EConfigType.TROJAN -> {
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
val remark = "#" + Utils.urlEncode(config.remarks)
|
||||||
|
|
||||||
val dicQuery = HashMap<String, String>()
|
val dicQuery = HashMap<String, String>()
|
||||||
|
if (config.configType == EConfigType.VLESS) {
|
||||||
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
|
outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow?.let {
|
||||||
if (!TextUtils.isEmpty(it)) {
|
if (!TextUtils.isEmpty(it)) {
|
||||||
dicQuery["flow"] = it
|
dicQuery["flow"] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dicQuery["encryption"] = if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
|
dicQuery["encryption"] =
|
||||||
|
if (outbound.getSecurityEncryption().isNullOrEmpty()) "none"
|
||||||
else outbound.getSecurityEncryption().orEmpty()
|
else outbound.getSecurityEncryption().orEmpty()
|
||||||
dicQuery["security"] = if (streamSetting.security.isEmpty()) "none"
|
} else if (config.configType == EConfigType.TROJAN) {
|
||||||
else streamSetting.security
|
config.outboundBean?.settings?.servers?.get(0)?.flow?.let {
|
||||||
|
if (!TextUtils.isEmpty(it)) {
|
||||||
|
dicQuery["flow"] = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dicQuery["security"] = streamSetting.security.ifEmpty { "none" }
|
||||||
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
||||||
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
||||||
dicQuery["sni"] = tlsSetting.serverName
|
dicQuery["sni"] = tlsSetting.serverName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dicQuery["type"] = if (streamSetting.network.isEmpty()) V2rayConfig.DEFAULT_NETWORK
|
dicQuery["type"] = streamSetting.network.ifEmpty { V2rayConfig.DEFAULT_NETWORK }
|
||||||
else streamSetting.network
|
|
||||||
|
|
||||||
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
outbound.getTransportSettingDetails()?.let { transportDetails ->
|
||||||
when (streamSetting.network) {
|
when (streamSetting.network) {
|
||||||
"tcp" -> {
|
"tcp" -> {
|
||||||
dicQuery["headerType"] = if (transportDetails[0].isEmpty()) "none"
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
else transportDetails[0]
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[1])) {
|
if (!TextUtils.isEmpty(transportDetails[1])) {
|
||||||
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
dicQuery["host"] = Utils.urlEncode(transportDetails[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"kcp" -> {
|
"kcp" -> {
|
||||||
dicQuery["headerType"] = if (transportDetails[0].isEmpty()) "none"
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
else transportDetails[0]
|
|
||||||
if (!TextUtils.isEmpty(transportDetails[2])) {
|
if (!TextUtils.isEmpty(transportDetails[2])) {
|
||||||
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
dicQuery["seed"] = Utils.urlEncode(transportDetails[2])
|
||||||
}
|
}
|
||||||
@@ -499,8 +556,7 @@ object AngConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"quic" -> {
|
"quic" -> {
|
||||||
dicQuery["headerType"] = if (transportDetails[0].isEmpty()) "none"
|
dicQuery["headerType"] = transportDetails[0].ifEmpty { "none" }
|
||||||
else transportDetails[0]
|
|
||||||
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
dicQuery["quicSecurity"] = Utils.urlEncode(transportDetails[1])
|
||||||
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
dicQuery["key"] = Utils.urlEncode(transportDetails[2])
|
||||||
}
|
}
|
||||||
@@ -516,21 +572,7 @@ object AngConfigManager {
|
|||||||
|
|
||||||
val url = String.format("%s@%s:%s",
|
val url = String.format("%s@%s:%s",
|
||||||
outbound.getPassword(),
|
outbound.getPassword(),
|
||||||
outbound.getServerAddress(),
|
Utils.getIpv6Address(outbound.getServerAddress()!!),
|
||||||
outbound.getServerPort())
|
|
||||||
url + query + remark
|
|
||||||
}
|
|
||||||
EConfigType.TROJAN -> {
|
|
||||||
val remark = "#" + Utils.urlEncode(config.remarks)
|
|
||||||
var query = ""
|
|
||||||
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
|
|
||||||
if (!TextUtils.isEmpty(tlsSetting.serverName)) {
|
|
||||||
query = "?sni=${tlsSetting.serverName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val url = String.format("%s@%s:%s",
|
|
||||||
outbound.getPassword(),
|
|
||||||
outbound.getServerAddress(),
|
|
||||||
outbound.getServerPort())
|
outbound.getServerPort())
|
||||||
url + query + remark
|
url + query + remark
|
||||||
}
|
}
|
||||||
@@ -653,13 +695,13 @@ object AngConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun importBatchConfig(servers: String?, subid: String): Int {
|
fun importBatchConfig(servers: String?, subid: String, append: Boolean): Int {
|
||||||
try {
|
try {
|
||||||
if (servers == null) {
|
if (servers == null) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val removedSelectedServer =
|
val removedSelectedServer =
|
||||||
if (!TextUtils.isEmpty(subid)) {
|
if (!TextUtils.isEmpty(subid) && !append) {
|
||||||
MmkvManager.decodeServerConfig(mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: "")?.let {
|
MmkvManager.decodeServerConfig(mainStorage?.decodeString(KEY_SELECTED_SERVER) ?: "")?.let {
|
||||||
if (it.subscriptionId == subid) {
|
if (it.subscriptionId == subid) {
|
||||||
return@let it
|
return@let it
|
||||||
@@ -669,8 +711,9 @@ object AngConfigManager {
|
|||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
if(!append) {
|
||||||
MmkvManager.removeServerViaSubid(subid)
|
MmkvManager.removeServerViaSubid(subid)
|
||||||
|
}
|
||||||
// var servers = server
|
// var servers = server
|
||||||
// if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) {
|
// if (server.indexOf("vmess") >= 0 && server.indexOf("vmess") == server.lastIndexOf("vmess")) {
|
||||||
// servers = server.replace("\n", "")
|
// servers = server.replace("\n", "")
|
||||||
|
|||||||
@@ -1,21 +1,36 @@
|
|||||||
package com.v2ray.ang.util
|
package com.v2ray.ang.util
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.service.V2RayTestService
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
|
|
||||||
object MessageUtil {
|
object MessageUtil {
|
||||||
|
|
||||||
fun sendMsg2Service(ctx: Context, what: Int, content: String) {
|
fun sendMsg2Service(ctx: Context, what: Int, content: Serializable) {
|
||||||
sendMsg(ctx, AppConfig.BROADCAST_ACTION_SERVICE, what, content)
|
sendMsg(ctx, AppConfig.BROADCAST_ACTION_SERVICE, what, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendMsg2UI(ctx: Context, what: Int, content: String) {
|
fun sendMsg2UI(ctx: Context, what: Int, content: Serializable) {
|
||||||
sendMsg(ctx, AppConfig.BROADCAST_ACTION_ACTIVITY, what, content)
|
sendMsg(ctx, AppConfig.BROADCAST_ACTION_ACTIVITY, what, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendMsg(ctx: Context, action: String, what: Int, content: String) {
|
fun sendMsg2TestService(ctx: Context, what: Int, content: Serializable) {
|
||||||
|
try {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.component = ComponentName(ctx, V2RayTestService::class.java)
|
||||||
|
intent.putExtra("key", what)
|
||||||
|
intent.putExtra("content", content)
|
||||||
|
ctx.startService(intent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendMsg(ctx: Context, action: String, what: Int, content: Serializable) {
|
||||||
try {
|
try {
|
||||||
val intent = Intent()
|
val intent = Intent()
|
||||||
intent.action = action
|
intent.action = action
|
||||||
|
|||||||
@@ -42,11 +42,7 @@ object MmkvManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
fun encodeServerConfig(guid: String, config: ServerConfig): String {
|
||||||
val key = if (guid.isBlank()) {
|
val key = guid.ifBlank { Utils.getUuid() }
|
||||||
Utils.getUuid()
|
|
||||||
} else {
|
|
||||||
guid
|
|
||||||
}
|
|
||||||
serverStorage?.encode(key, Gson().toJson(config))
|
serverStorage?.encode(key, Gson().toJson(config))
|
||||||
val serverList = decodeServerList()
|
val serverList = decodeServerList()
|
||||||
if (!serverList.contains(key)) {
|
if (!serverList.contains(key)) {
|
||||||
@@ -145,4 +141,39 @@ object MmkvManager {
|
|||||||
subStorage?.remove(subid)
|
subStorage?.remove(subid)
|
||||||
removeServerViaSubid(subid)
|
removeServerViaSubid(subid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeAllServer() {
|
||||||
|
mainStorage?.clearAll()
|
||||||
|
serverStorage?.clearAll()
|
||||||
|
serverAffStorage?.clearAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeInvalidServer() {
|
||||||
|
serverAffStorage?.allKeys()?.forEach { key ->
|
||||||
|
decodeServerAffiliationInfo(key)?.let { aff ->
|
||||||
|
if (aff.testDelayMillis <= 0L) {
|
||||||
|
removeServer(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sortByTestResults( ) {
|
||||||
|
data class ServerDelay(var guid: String, var testDelayMillis: Long)
|
||||||
|
|
||||||
|
val serverDelays = mutableListOf<ServerDelay>()
|
||||||
|
val serverList = decodeServerList()
|
||||||
|
serverList.forEach { key ->
|
||||||
|
val delay = decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
|
||||||
|
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
|
||||||
|
}
|
||||||
|
serverDelays.sortBy { it.testDelayMillis }
|
||||||
|
|
||||||
|
serverDelays.forEach {
|
||||||
|
serverList.remove(it.guid)
|
||||||
|
serverList.add(it.guid)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.v2ray.ang.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.ContextWrapper
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.LocaleList
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
open class MyContextWrapper(base: Context?) : ContextWrapper(base) {
|
||||||
|
companion object {
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
fun wrap(context: Context, newLocale: Locale?): ContextWrapper {
|
||||||
|
var mContext = context
|
||||||
|
val res: Resources = mContext.resources
|
||||||
|
val configuration: Configuration = res.configuration
|
||||||
|
//注意 Android 7.0 前后的不同处理方法
|
||||||
|
mContext = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
configuration.setLocale(newLocale)
|
||||||
|
val localeList = LocaleList(newLocale)
|
||||||
|
LocaleList.setDefault(localeList)
|
||||||
|
configuration.setLocales(localeList)
|
||||||
|
mContext.createConfigurationContext(configuration)
|
||||||
|
} else {
|
||||||
|
configuration.setLocale(newLocale)
|
||||||
|
mContext.createConfigurationContext(configuration)
|
||||||
|
}
|
||||||
|
return ContextWrapper(mContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
100
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/QRCodeDecoder.kt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package com.v2ray.ang.util
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import com.google.zxing.*
|
||||||
|
import com.google.zxing.common.GlobalHistogramBinarizer
|
||||||
|
import com.google.zxing.common.HybridBinarizer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述:解析二维码图片
|
||||||
|
*/
|
||||||
|
object QRCodeDecoder {
|
||||||
|
val HINTS: MutableMap<DecodeHintType, Any?> = EnumMap(DecodeHintType::class.java)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步解析本地图片二维码。该方法是耗时操作,请在子线程中调用。
|
||||||
|
*
|
||||||
|
* @param picturePath 要解析的二维码图片本地路径
|
||||||
|
* @return 返回二维码图片里的内容 或 null
|
||||||
|
*/
|
||||||
|
fun syncDecodeQRCode(picturePath: String): String? {
|
||||||
|
return syncDecodeQRCode(getDecodeAbleBitmap(picturePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步解析bitmap二维码。该方法是耗时操作,请在子线程中调用。
|
||||||
|
*
|
||||||
|
* @param bitmap 要解析的二维码图片
|
||||||
|
* @return 返回二维码图片里的内容 或 null
|
||||||
|
*/
|
||||||
|
fun syncDecodeQRCode(bitmap: Bitmap?): String? {
|
||||||
|
var source: RGBLuminanceSource? = null
|
||||||
|
try {
|
||||||
|
val width = bitmap!!.width
|
||||||
|
val height = bitmap.height
|
||||||
|
val pixels = IntArray(width * height)
|
||||||
|
bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||||
|
source = RGBLuminanceSource(width, height, pixels)
|
||||||
|
return MultiFormatReader().decode(BinaryBitmap(HybridBinarizer(source)), HINTS).text
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
if (source != null) {
|
||||||
|
try {
|
||||||
|
return MultiFormatReader().decode(BinaryBitmap(GlobalHistogramBinarizer(source)), HINTS).text
|
||||||
|
} catch (e2: Throwable) {
|
||||||
|
e2.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将本地图片文件转换成可解码二维码的 Bitmap。为了避免图片太大,这里对图片进行了压缩。感谢 https://github.com/devilsen 提的 PR
|
||||||
|
*
|
||||||
|
* @param picturePath 本地图片文件路径
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private fun getDecodeAbleBitmap(picturePath: String): Bitmap? {
|
||||||
|
return try {
|
||||||
|
val options = BitmapFactory.Options()
|
||||||
|
options.inJustDecodeBounds = true
|
||||||
|
BitmapFactory.decodeFile(picturePath, options)
|
||||||
|
var sampleSize = options.outHeight / 400
|
||||||
|
if (sampleSize <= 0) {
|
||||||
|
sampleSize = 1
|
||||||
|
}
|
||||||
|
options.inSampleSize = sampleSize
|
||||||
|
options.inJustDecodeBounds = false
|
||||||
|
BitmapFactory.decodeFile(picturePath, options)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val allFormats: List<BarcodeFormat> = arrayListOf(
|
||||||
|
BarcodeFormat.AZTEC
|
||||||
|
,BarcodeFormat.CODABAR
|
||||||
|
,BarcodeFormat.CODE_39
|
||||||
|
,BarcodeFormat.CODE_93
|
||||||
|
,BarcodeFormat.CODE_128
|
||||||
|
,BarcodeFormat.DATA_MATRIX
|
||||||
|
,BarcodeFormat.EAN_8
|
||||||
|
,BarcodeFormat.EAN_13
|
||||||
|
,BarcodeFormat.ITF
|
||||||
|
,BarcodeFormat.MAXICODE
|
||||||
|
,BarcodeFormat.PDF_417
|
||||||
|
,BarcodeFormat.QR_CODE
|
||||||
|
,BarcodeFormat.RSS_14
|
||||||
|
,BarcodeFormat.RSS_EXPANDED
|
||||||
|
,BarcodeFormat.UPC_A
|
||||||
|
,BarcodeFormat.UPC_E
|
||||||
|
,BarcodeFormat.UPC_EAN_EXTENSION)
|
||||||
|
HINTS[DecodeHintType.TRY_HARDER] = BarcodeFormat.QR_CODE
|
||||||
|
HINTS[DecodeHintType.POSSIBLE_FORMATS] = allFormats
|
||||||
|
HINTS[DecodeHintType.CHARACTER_SET] = "utf-8"
|
||||||
|
}
|
||||||
|
}
|
||||||
142
V2rayNG/app/src/main/kotlin/com/v2ray/ang/util/SpeedtestUtil.kt
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package com.v2ray.ang.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.R
|
||||||
|
import com.v2ray.ang.extension.responseLength
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import libv2ray.Libv2ray
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.*
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
|
object SpeedtestUtil {
|
||||||
|
|
||||||
|
private val tcpTestingSockets = ArrayList<Socket?>()
|
||||||
|
|
||||||
|
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 && (time == -1L || one < time)) {
|
||||||
|
time = one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time
|
||||||
|
}
|
||||||
|
|
||||||
|
fun realPing(config: String): Long {
|
||||||
|
return try {
|
||||||
|
Libv2ray.measureOutboundDelay(config)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "realPing: $e")
|
||||||
|
-1L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ping(url: String): String {
|
||||||
|
try {
|
||||||
|
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)) {
|
||||||
|
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
||||||
|
val temps = tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
if (temps.count() > 0 && temps[0].length < 10) {
|
||||||
|
return temps[0].toFloat().toInt().toString() + "ms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return "-1ms"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun socketConnectTime(url: String, port: Int): Long {
|
||||||
|
try {
|
||||||
|
val socket = Socket()
|
||||||
|
synchronized(this) {
|
||||||
|
tcpTestingSockets.add(socket)
|
||||||
|
}
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
socket.connect(InetSocketAddress(url, port),3000)
|
||||||
|
val time = System.currentTimeMillis() - start
|
||||||
|
synchronized(this) {
|
||||||
|
tcpTestingSockets.remove(socket)
|
||||||
|
}
|
||||||
|
socket.close()
|
||||||
|
return time
|
||||||
|
} catch (e: UnknownHostException) {
|
||||||
|
e.printStackTrace()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fun closeAllTcpSockets() {
|
||||||
|
synchronized(this) {
|
||||||
|
tcpTestingSockets.forEach {
|
||||||
|
it?.close()
|
||||||
|
}
|
||||||
|
tcpTestingSockets.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConnection(context: Context, port: Int): String {
|
||||||
|
// return V2RayVpnService.measureV2rayDelay()
|
||||||
|
var result: String
|
||||||
|
var conn: HttpURLConnection? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
val url = URL("https",
|
||||||
|
"www.google.com",
|
||||||
|
"/generate_204")
|
||||||
|
|
||||||
|
conn = url.openConnection(
|
||||||
|
Proxy(Proxy.Type.HTTP,
|
||||||
|
InetSocketAddress("127.0.0.1", port))) as HttpURLConnection
|
||||||
|
conn.connectTimeout = 30000
|
||||||
|
conn.readTimeout = 30000
|
||||||
|
conn.setRequestProperty("Connection", "close")
|
||||||
|
conn.instanceFollowRedirects = false
|
||||||
|
conn.useCaches = false
|
||||||
|
|
||||||
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
val code = conn.responseCode
|
||||||
|
val elapsed = SystemClock.elapsedRealtime() - start
|
||||||
|
|
||||||
|
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
||||||
|
result = context.getString(R.string.connection_test_available, elapsed)
|
||||||
|
} else {
|
||||||
|
throw IOException(context.getString(R.string.connection_test_error_status_code, code))
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// network exception
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "testConnection IOException: " + Log.getStackTraceString(e))
|
||||||
|
result = context.getString(R.string.connection_test_error, e.message)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// library exception, eg sumsung
|
||||||
|
Log.d(AppConfig.ANG_PACKAGE, "testConnection Exception: " + Log.getStackTraceString(e))
|
||||||
|
result = context.getString(R.string.connection_test_error, e.message)
|
||||||
|
} finally {
|
||||||
|
conn?.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLibVersion(): String {
|
||||||
|
return Libv2ray.checkVersionX()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,28 +13,28 @@ import java.util.*
|
|||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.SystemClock
|
import android.os.Build
|
||||||
|
import android.os.LocaleList
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import android.webkit.URLUtil
|
import android.webkit.URLUtil
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.BuildConfig
|
import com.v2ray.ang.BuildConfig
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.extension.responseLength
|
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.service.V2RayServiceManager
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.*
|
import java.net.*
|
||||||
import kotlin.coroutines.coroutineContext
|
import com.v2ray.ang.service.V2RayServiceManager
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
object Utils {
|
object Utils {
|
||||||
|
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
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 settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
|
||||||
private val tcpTestingSockets = ArrayList<Socket?>()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert string to editalbe for kotlin
|
* convert string to editalbe for kotlin
|
||||||
@@ -62,11 +62,16 @@ object Utils {
|
|||||||
* parseInt
|
* parseInt
|
||||||
*/
|
*/
|
||||||
fun parseInt(str: String): Int {
|
fun parseInt(str: String): Int {
|
||||||
try {
|
return parseInt(str, 0)
|
||||||
return Integer.parseInt(str)
|
}
|
||||||
|
|
||||||
|
fun parseInt(str: String?, default: Int): Int {
|
||||||
|
str ?: return default
|
||||||
|
return try {
|
||||||
|
Integer.parseInt(str)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return 0
|
default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +79,12 @@ object Utils {
|
|||||||
* get text from clipboard
|
* get text from clipboard
|
||||||
*/
|
*/
|
||||||
fun getClipboard(context: Context): String {
|
fun getClipboard(context: Context): String {
|
||||||
try {
|
return try {
|
||||||
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
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) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return ""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,12 +117,12 @@ object Utils {
|
|||||||
try {
|
try {
|
||||||
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.i(AppConfig.ANG_PACKAGE, "Parse base64 standard failed $e")
|
Log.i(ANG_PACKAGE, "Parse base64 standard failed $e")
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8"))
|
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8"))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.i(AppConfig.ANG_PACKAGE, "Parse base64 url safe failed $e")
|
Log.i(ANG_PACKAGE, "Parse base64 url safe failed $e")
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -126,11 +131,11 @@ object Utils {
|
|||||||
* base64 encode
|
* base64 encode
|
||||||
*/
|
*/
|
||||||
fun encode(text: String): String {
|
fun encode(text: String): String {
|
||||||
try {
|
return try {
|
||||||
return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
|
Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return ""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +144,7 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun getRemoteDnsServers(): List<String> {
|
fun getRemoteDnsServers(): List<String> {
|
||||||
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
|
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
|
||||||
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
|
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||||
if (ret.isEmpty()) {
|
if (ret.isEmpty()) {
|
||||||
return listOf(AppConfig.DNS_AGENT)
|
return listOf(AppConfig.DNS_AGENT)
|
||||||
}
|
}
|
||||||
@@ -159,7 +164,7 @@ object Utils {
|
|||||||
*/
|
*/
|
||||||
fun getDomesticDnsServers(): List<String> {
|
fun getDomesticDnsServers(): List<String> {
|
||||||
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
|
||||||
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || it.startsWith("https") }
|
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
|
||||||
if (ret.isEmpty()) {
|
if (ret.isEmpty()) {
|
||||||
return listOf(AppConfig.DNS_DIRECT)
|
return listOf(AppConfig.DNS_DIRECT)
|
||||||
}
|
}
|
||||||
@@ -172,12 +177,12 @@ object Utils {
|
|||||||
fun createQRCode(text: String, size: Int = 800): Bitmap? {
|
fun createQRCode(text: String, size: Int = 800): Bitmap? {
|
||||||
try {
|
try {
|
||||||
val hints = HashMap<EncodeHintType, String>()
|
val hints = HashMap<EncodeHintType, String>()
|
||||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8")
|
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
|
||||||
val bitMatrix = QRCodeWriter().encode(text,
|
val bitMatrix = QRCodeWriter().encode(text,
|
||||||
BarcodeFormat.QR_CODE, size, size, hints)
|
BarcodeFormat.QR_CODE, size, size, hints)
|
||||||
val pixels = IntArray(size * size)
|
val pixels = IntArray(size * size)
|
||||||
for (y in 0..size - 1) {
|
for (y in 0 until size) {
|
||||||
for (x in 0..size - 1) {
|
for (x in 0 until size) {
|
||||||
if (bitMatrix.get(x, y)) {
|
if (bitMatrix.get(x, y)) {
|
||||||
pixels[y * size + x] = 0xff000000.toInt()
|
pixels[y * size + x] = 0xff000000.toInt()
|
||||||
} else {
|
} else {
|
||||||
@@ -222,7 +227,7 @@ object Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addr = addr.toLowerCase()
|
// addr = addr.toLowerCase()
|
||||||
var octets = addr.split('.').toTypedArray()
|
val octets = addr.split('.').toTypedArray()
|
||||||
if (octets.size == 4) {
|
if (octets.size == 4) {
|
||||||
if (octets[3].indexOf(":") > 0) {
|
if (octets[3].indexOf(":") > 0) {
|
||||||
addr = addr.substring(0, addr.indexOf(":"))
|
addr = addr.substring(0, addr.indexOf(":"))
|
||||||
@@ -257,6 +262,10 @@ object Utils {
|
|||||||
return regV6.matches(addr)
|
return regV6.matches(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isCoreDNSAddress(s: String): Boolean {
|
||||||
|
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is valid url
|
* is valid url
|
||||||
*/
|
*/
|
||||||
@@ -298,84 +307,32 @@ object Utils {
|
|||||||
* uuid
|
* uuid
|
||||||
*/
|
*/
|
||||||
fun getUuid(): String {
|
fun getUuid(): String {
|
||||||
try {
|
return try {
|
||||||
return UUID.randomUUID().toString().replace("-", "")
|
UUID.randomUUID().toString().replace("-", "")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return ""
|
""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun urlDecode(url: String): String {
|
fun urlDecode(url: String): String {
|
||||||
try {
|
return try {
|
||||||
return URLDecoder.decode(url, "UTF-8")
|
URLDecoder.decode(URLDecoder.decode(url), "utf-8")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return url
|
url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun urlEncode(url: String): String {
|
fun urlEncode(url: String): String {
|
||||||
try {
|
return try {
|
||||||
return URLEncoder.encode(url, "UTF-8")
|
URLEncoder.encode(url, "UTF-8")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return url
|
url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testConnection(context: Context, port: Int): String {
|
|
||||||
var result: String
|
|
||||||
var conn: HttpURLConnection? = null
|
|
||||||
|
|
||||||
try {
|
|
||||||
val url = URL("https",
|
|
||||||
"www.google.com",
|
|
||||||
"/generate_204")
|
|
||||||
|
|
||||||
conn = url.openConnection(
|
|
||||||
Proxy(Proxy.Type.HTTP,
|
|
||||||
InetSocketAddress("127.0.0.1", port + 1))) as HttpURLConnection
|
|
||||||
conn.connectTimeout = 30000
|
|
||||||
conn.readTimeout = 30000
|
|
||||||
conn.setRequestProperty("Connection", "close")
|
|
||||||
conn.instanceFollowRedirects = false
|
|
||||||
conn.useCaches = false
|
|
||||||
|
|
||||||
val start = SystemClock.elapsedRealtime()
|
|
||||||
val code = conn.responseCode
|
|
||||||
val elapsed = SystemClock.elapsedRealtime() - start
|
|
||||||
|
|
||||||
if (code == 204 || code == 200 && conn.responseLength == 0L) {
|
|
||||||
result = context.getString(R.string.connection_test_available, elapsed)
|
|
||||||
} else {
|
|
||||||
throw IOException(context.getString(R.string.connection_test_error_status_code, code))
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// network exception
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE,"testConnection IOException: "+Log.getStackTraceString(e))
|
|
||||||
result = context.getString(R.string.connection_test_error, e.message)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// library exception, eg sumsung
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE,"testConnection Exception: "+Log.getStackTraceString(e))
|
|
||||||
result = context.getString(R.string.connection_test_error, e.message)
|
|
||||||
} finally {
|
|
||||||
conn?.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* package path
|
|
||||||
*/
|
|
||||||
fun packagePath(context: Context): String {
|
|
||||||
var path = context.filesDir.toString()
|
|
||||||
path = path.replace("files", "")
|
|
||||||
//path += "tun2socks"
|
|
||||||
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* readTextFromAssets
|
* readTextFromAssets
|
||||||
@@ -387,75 +344,33 @@ object Utils {
|
|||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun userAssetPath(context: Context?): String {
|
||||||
* ping
|
if (context == null)
|
||||||
*/
|
return ""
|
||||||
fun ping(url: String): String {
|
val extDir = context.getExternalFilesDir(AppConfig.DIR_ASSETS)
|
||||||
|
?: return context.getDir(AppConfig.DIR_ASSETS, 0).absolutePath
|
||||||
|
return extDir.absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUrlContext(url: String, timeout: Int): String {
|
||||||
|
var result: String
|
||||||
|
var conn: HttpURLConnection? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val command = "/system/bin/ping -c 3 $url"
|
conn = URL(url).openConnection() as HttpURLConnection
|
||||||
val process = Runtime.getRuntime().exec(command)
|
conn.connectTimeout = timeout
|
||||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
conn.readTimeout = timeout
|
||||||
if (allText.isNotBlank()) {
|
conn.setRequestProperty("Connection", "close")
|
||||||
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
|
conn.instanceFollowRedirects = false
|
||||||
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
|
conn.useCaches = false
|
||||||
if (temps.count() > 0 && temps[0].length < 10) {
|
//val code = conn.responseCode
|
||||||
return temps[0].toFloat().toInt().toString() + "ms"
|
result = conn.inputStream.bufferedReader().readText()
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
result = ""
|
||||||
}
|
} finally {
|
||||||
return "-1ms"
|
conn?.disconnect()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* tcping
|
|
||||||
*/
|
|
||||||
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 && (time == -1L || one < time)) {
|
|
||||||
time = one
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return time
|
|
||||||
}
|
|
||||||
|
|
||||||
fun socketConnectTime(url: String, port: Int): Long {
|
|
||||||
try {
|
|
||||||
val socket = Socket()
|
|
||||||
synchronized(this) {
|
|
||||||
tcpTestingSockets.add(socket)
|
|
||||||
}
|
|
||||||
val start = System.currentTimeMillis()
|
|
||||||
socket.connect(InetSocketAddress(url, port))
|
|
||||||
val time = System.currentTimeMillis() - start
|
|
||||||
synchronized(this) {
|
|
||||||
tcpTestingSockets.remove(socket)
|
|
||||||
}
|
|
||||||
socket.close()
|
|
||||||
return time
|
|
||||||
} catch (e: UnknownHostException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
fun closeAllTcpSockets() {
|
|
||||||
synchronized(this) {
|
|
||||||
tcpTestingSockets.forEach {
|
|
||||||
it?.close()
|
|
||||||
}
|
|
||||||
tcpTestingSockets.clear()
|
|
||||||
}
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@@ -473,5 +388,40 @@ object Utils {
|
|||||||
it.bufferedReader().readText()
|
it.bufferedReader().readText()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDarkModeStatus(context: Context): Boolean {
|
||||||
|
val mode = context.resources.configuration.uiMode and UI_MODE_NIGHT_MASK
|
||||||
|
return mode == UI_MODE_NIGHT_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIpv6Address(address: String): String {
|
||||||
|
return if (isIpv6Address(address)) {
|
||||||
|
String.format("[%s]", address)
|
||||||
|
} else {
|
||||||
|
address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLocale(context: Context): Locale =
|
||||||
|
when (settingsStorage?.decodeString(AppConfig.PREF_LANGUAGE) ?: "auto") {
|
||||||
|
"auto" -> getSysLocale()
|
||||||
|
"en" -> Locale("en")
|
||||||
|
"zh-rCN" -> Locale("zh", "CN")
|
||||||
|
"zh-rTW" -> Locale("zh", "TW")
|
||||||
|
"vi" -> Locale("vi")
|
||||||
|
else -> getSysLocale()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSysLocale(): Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
LocaleList.getDefault()[0]
|
||||||
|
} else {
|
||||||
|
Locale.getDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fixIllegalUrl(str: String): String {
|
||||||
|
return str
|
||||||
|
.replace(" ","%20")
|
||||||
|
.replace("|","%7C")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.v2ray.ang.AppConfig
|
|||||||
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
import com.v2ray.ang.dto.V2rayConfig
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.dto.EConfigType
|
||||||
|
import com.v2ray.ang.dto.ERoutingMode
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
|
||||||
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
|
||||||
|
|
||||||
@@ -58,7 +59,8 @@ object V2rayConfigUtil {
|
|||||||
//转成Json
|
//转成Json
|
||||||
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
|
||||||
|
|
||||||
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
|
v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL)
|
||||||
|
?: "warning"
|
||||||
|
|
||||||
inbounds(v2rayConfig)
|
inbounds(v2rayConfig)
|
||||||
|
|
||||||
@@ -89,16 +91,16 @@ object V2rayConfigUtil {
|
|||||||
*/
|
*/
|
||||||
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
|
||||||
try {
|
try {
|
||||||
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
|
val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT), AppConfig.PORT_SOCKS.toInt())
|
||||||
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
|
val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT), AppConfig.PORT_HTTP.toInt())
|
||||||
|
|
||||||
v2rayConfig.inbounds.forEach { curInbound ->
|
v2rayConfig.inbounds.forEach { curInbound ->
|
||||||
if (!(settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) ?: false)) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
|
||||||
//bind all inbounds to localhost if the user requests
|
//bind all inbounds to localhost if the user requests
|
||||||
curInbound.listen = "127.0.0.1"
|
curInbound.listen = "127.0.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v2rayConfig.inbounds[0].port = 10808
|
v2rayConfig.inbounds[0].port = socksPort
|
||||||
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
|
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
|
||||||
?: false
|
?: false
|
||||||
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
|
||||||
@@ -111,7 +113,7 @@ object V2rayConfigUtil {
|
|||||||
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
|
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
|
||||||
}
|
}
|
||||||
|
|
||||||
//v2rayConfig.inbounds[1].port = httpPort
|
v2rayConfig.inbounds[1].port = httpPort
|
||||||
|
|
||||||
// if (httpPort > 0) {
|
// if (httpPort > 0) {
|
||||||
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
// val httpCopy = v2rayConfig.inbounds[0].copy()
|
||||||
@@ -128,7 +130,7 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
private fun fakedns(v2rayConfig: V2rayConfig) {
|
private fun fakedns(v2rayConfig: V2rayConfig) {
|
||||||
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||||
v2rayConfig.fakedns = V2rayConfig.FakednsBean()
|
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
|
||||||
v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
|
v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
|
||||||
it.settings?.domainStrategy = "UseIP"
|
it.settings?.domainStrategy = "UseIP"
|
||||||
}
|
}
|
||||||
@@ -149,7 +151,8 @@ object V2rayConfigUtil {
|
|||||||
|
|
||||||
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
|
||||||
?: "IPIfNonMatch"
|
?: "IPIfNonMatch"
|
||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
v2rayConfig.routing.domainMatcher = "mph"
|
||||||
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value
|
||||||
|
|
||||||
// Hardcode googleapis.cn
|
// Hardcode googleapis.cn
|
||||||
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
|
||||||
@@ -159,18 +162,26 @@ object V2rayConfigUtil {
|
|||||||
)
|
)
|
||||||
|
|
||||||
when (routingMode) {
|
when (routingMode) {
|
||||||
"1" -> {
|
ERoutingMode.BYPASS_LAN.value -> {
|
||||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||||
}
|
}
|
||||||
"2" -> {
|
ERoutingMode.BYPASS_MAINLAND.value -> {
|
||||||
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||||
}
|
}
|
||||||
"3" -> {
|
ERoutingMode.BYPASS_LAN_MAINLAND.value -> {
|
||||||
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||||
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
routingGeo("", "cn", AppConfig.TAG_DIRECT, v2rayConfig)
|
||||||
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
v2rayConfig.routing.rules.add(0, googleapisRoute)
|
||||||
}
|
}
|
||||||
|
ERoutingMode.GLOBAL_DIRECT.value -> {
|
||||||
|
val globalDirect = V2rayConfig.RoutingBean.RulesBean(
|
||||||
|
type = "field",
|
||||||
|
outboundTag = AppConfig.TAG_DIRECT,
|
||||||
|
port = "0-65535"
|
||||||
|
)
|
||||||
|
v2rayConfig.routing.rules.add(globalDirect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -187,7 +198,7 @@ object V2rayConfigUtil {
|
|||||||
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
||||||
rulesIP.type = "field"
|
rulesIP.type = "field"
|
||||||
rulesIP.outboundTag = tag
|
rulesIP.outboundTag = tag
|
||||||
rulesIP.ip = ArrayList<String>()
|
rulesIP.ip = ArrayList()
|
||||||
rulesIP.ip?.add("geoip:$code")
|
rulesIP.ip?.add("geoip:$code")
|
||||||
v2rayConfig.routing.rules.add(rulesIP)
|
v2rayConfig.routing.rules.add(rulesIP)
|
||||||
}
|
}
|
||||||
@@ -197,7 +208,7 @@ object V2rayConfigUtil {
|
|||||||
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
||||||
rulesDomain.type = "field"
|
rulesDomain.type = "field"
|
||||||
rulesDomain.outboundTag = tag
|
rulesDomain.outboundTag = tag
|
||||||
rulesDomain.domain = ArrayList<String>()
|
rulesDomain.domain = ArrayList()
|
||||||
rulesDomain.domain?.add("geosite:$code")
|
rulesDomain.domain?.add("geosite:$code")
|
||||||
v2rayConfig.routing.rules.add(rulesDomain)
|
v2rayConfig.routing.rules.add(rulesDomain)
|
||||||
}
|
}
|
||||||
@@ -214,13 +225,13 @@ object V2rayConfigUtil {
|
|||||||
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
|
||||||
rulesDomain.type = "field"
|
rulesDomain.type = "field"
|
||||||
rulesDomain.outboundTag = tag
|
rulesDomain.outboundTag = tag
|
||||||
rulesDomain.domain = ArrayList<String>()
|
rulesDomain.domain = ArrayList()
|
||||||
|
|
||||||
//IP
|
//IP
|
||||||
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
|
||||||
rulesIP.type = "field"
|
rulesIP.type = "field"
|
||||||
rulesIP.outboundTag = tag
|
rulesIP.outboundTag = tag
|
||||||
rulesIP.ip = ArrayList<String>()
|
rulesIP.ip = ArrayList()
|
||||||
|
|
||||||
userRule.split(",").map { it.trim() }.forEach {
|
userRule.split(",").map { it.trim() }.forEach {
|
||||||
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
|
||||||
@@ -277,15 +288,15 @@ object V2rayConfigUtil {
|
|||||||
val remoteDns = Utils.getRemoteDnsServers()
|
val remoteDns = Utils.getRemoteDnsServers()
|
||||||
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
|
||||||
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
|
||||||
address = if (remoteDns.first().startsWith("https")) "1.1.1.1" else remoteDns.first(),
|
address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else "1.1.1.1",
|
||||||
port = 53,
|
port = 53,
|
||||||
network = "tcp,udp")
|
network = "tcp,udp")
|
||||||
|
|
||||||
//val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT) ?: AppConfig.PORT_LOCAL_DNS)
|
val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt())
|
||||||
v2rayConfig.inbounds.add(
|
v2rayConfig.inbounds.add(
|
||||||
V2rayConfig.InboundBean(
|
V2rayConfig.InboundBean(
|
||||||
tag = "dns-in",
|
tag = "dns-in",
|
||||||
port = 10807,
|
port = localDnsPort,
|
||||||
listen = "127.0.0.1",
|
listen = "127.0.0.1",
|
||||||
protocol = "dokodemo-door",
|
protocol = "dokodemo-door",
|
||||||
settings = dnsInboundSettings,
|
settings = dnsInboundSettings,
|
||||||
@@ -335,18 +346,18 @@ object V2rayConfigUtil {
|
|||||||
// domestic DNS
|
// domestic DNS
|
||||||
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
|
||||||
?: "")
|
?: "")
|
||||||
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
|
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: ERoutingMode.GLOBAL_PROXY.value
|
||||||
if (directDomain.size > 0 || routingMode == "2" || routingMode == "3") {
|
if (directDomain.size > 0 || routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
||||||
val domesticDns = Utils.getDomesticDnsServers()
|
val domesticDns = Utils.getDomesticDnsServers()
|
||||||
val geositeCn = arrayListOf("geosite:cn")
|
val geositeCn = arrayListOf("geosite:cn")
|
||||||
val geoipCn = arrayListOf("geoip:cn")
|
val geoipCn = arrayListOf("geoip:cn")
|
||||||
if (directDomain.size > 0) {
|
if (directDomain.size > 0) {
|
||||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
|
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
|
||||||
}
|
}
|
||||||
if (routingMode == "2" || routingMode == "3") {
|
if (routingMode == ERoutingMode.BYPASS_MAINLAND.value || routingMode == ERoutingMode.BYPASS_LAN_MAINLAND.value) {
|
||||||
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
|
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
|
||||||
}
|
}
|
||||||
if (!domesticDns.first().startsWith("https")) {
|
if (Utils.isPureIpAddress(domesticDns.first())) {
|
||||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
type = "field",
|
type = "field",
|
||||||
outboundTag = AppConfig.TAG_DIRECT,
|
outboundTag = AppConfig.TAG_DIRECT,
|
||||||
@@ -364,7 +375,7 @@ object V2rayConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// hardcode googleapi rule to fix play store problems
|
// hardcode googleapi rule to fix play store problems
|
||||||
hosts.put("domain:googleapis.cn", "googleapis.com")
|
hosts["domain:googleapis.cn"] = "googleapis.com"
|
||||||
|
|
||||||
// DNS dns对象
|
// DNS dns对象
|
||||||
v2rayConfig.dns = V2rayConfig.DnsBean(
|
v2rayConfig.dns = V2rayConfig.DnsBean(
|
||||||
@@ -372,7 +383,7 @@ object V2rayConfigUtil {
|
|||||||
hosts = hosts)
|
hosts = hosts)
|
||||||
|
|
||||||
// DNS routing
|
// DNS routing
|
||||||
if (!remoteDns.first().startsWith("https")) {
|
if (Utils.isPureIpAddress(remoteDns.first())) {
|
||||||
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
|
||||||
type = "field",
|
type = "field",
|
||||||
outboundTag = AppConfig.TAG_AGENT,
|
outboundTag = AppConfig.TAG_AGENT,
|
||||||
@@ -393,14 +404,22 @@ object V2rayConfigUtil {
|
|||||||
if (outbound.streamSettings?.network == DEFAULT_NETWORK
|
if (outbound.streamSettings?.network == DEFAULT_NETWORK
|
||||||
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
|
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
|
||||||
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
|
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
|
||||||
val Host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
|
||||||
|
|
||||||
val requestString: String by lazy {
|
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"}}"""
|
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
|
||||||
}
|
}
|
||||||
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(requestString, V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java)
|
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(
|
||||||
outbound.streamSettings?.tcpSettings?.header?.request?.path = path!!
|
requestString,
|
||||||
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = Host!!
|
V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java
|
||||||
|
)
|
||||||
|
outbound.streamSettings?.tcpSettings?.header?.request?.path =
|
||||||
|
if (path.isNullOrEmpty()) {
|
||||||
|
listOf("/")
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!!
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -1,35 +1,37 @@
|
|||||||
package com.v2ray.ang.viewmodel
|
package com.v2ray.ang.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.*
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import android.content.BroadcastReceiver
|
import androidx.lifecycle.viewModelScope
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.util.Log
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AngApplication
|
import com.v2ray.ang.AngApplication
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
|
import com.v2ray.ang.AppConfig.ANG_PACKAGE
|
||||||
import com.v2ray.ang.R
|
import com.v2ray.ang.R
|
||||||
import com.v2ray.ang.dto.EConfigType
|
import com.v2ray.ang.databinding.DialogConfigFilterBinding
|
||||||
import com.v2ray.ang.dto.ServerConfig
|
import com.v2ray.ang.dto.*
|
||||||
import com.v2ray.ang.dto.V2rayConfig
|
|
||||||
import com.v2ray.ang.extension.toast
|
import com.v2ray.ang.extension.toast
|
||||||
import com.v2ray.ang.util.*
|
import com.v2ray.ang.util.*
|
||||||
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
|
private val 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 serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
|
||||||
|
|
||||||
var serverList = MmkvManager.decodeServerList()
|
var serverList = MmkvManager.decodeServerList()
|
||||||
|
var subscriptionId: String = ""
|
||||||
|
var keywordFilter: String = ""
|
||||||
private set
|
private set
|
||||||
val serversCache = ConcurrentHashMap<String, ServerConfig>()
|
val serversCache = mutableListOf<ServersCache>()
|
||||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||||
val updateListAction by lazy { MutableLiveData<Int>() }
|
val updateListAction by lazy { MutableLiveData<Int>() }
|
||||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||||
@@ -45,8 +47,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
|
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
|
||||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||||
Utils.closeAllTcpSockets()
|
SpeedtestUtil.closeAllTcpSockets()
|
||||||
Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared")
|
Log.i(ANG_PACKAGE, "Main ViewModel is cleared")
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,51 +61,58 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
fun removeServer(guid: String) {
|
fun removeServer(guid: String) {
|
||||||
serverList.remove(guid)
|
serverList.remove(guid)
|
||||||
MmkvManager.removeServer(guid)
|
MmkvManager.removeServer(guid)
|
||||||
|
serversCache.removeAt(getPosition(guid))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun appendCustomConfigServer(server: String) {
|
fun appendCustomConfigServer(server: String) {
|
||||||
val config = ServerConfig.create(EConfigType.CUSTOM)
|
val config = ServerConfig.create(EConfigType.CUSTOM)
|
||||||
config.remarks = System.currentTimeMillis().toString()
|
config.remarks = System.currentTimeMillis().toString()
|
||||||
|
config.subscriptionId = subscriptionId
|
||||||
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
|
||||||
val key = MmkvManager.encodeServerConfig("", config)
|
val key = MmkvManager.encodeServerConfig("", config)
|
||||||
serverRawStorage?.encode(key, server)
|
serverRawStorage?.encode(key, server)
|
||||||
serverList.add(key)
|
serverList.add(key)
|
||||||
serversCache[key] = config
|
serversCache.add(ServersCache(key,config))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun swapServer(fromPosition: Int, toPosition: Int) {
|
fun swapServer(fromPosition: Int, toPosition: Int) {
|
||||||
Collections.swap(serverList, fromPosition, toPosition)
|
Collections.swap(serverList, fromPosition, toPosition)
|
||||||
|
Collections.swap(serversCache, fromPosition, toPosition)
|
||||||
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun updateCache() {
|
fun updateCache() {
|
||||||
serversCache.clear()
|
serversCache.clear()
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
for (guid in serverList) {
|
||||||
serverList.forEach { guid ->
|
val config = MmkvManager.decodeServerConfig(guid) ?: continue
|
||||||
MmkvManager.decodeServerConfig(guid)?.let {
|
if (subscriptionId.isNotEmpty() && subscriptionId != config.subscriptionId) {
|
||||||
serversCache[guid] = it
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keywordFilter.isEmpty() || config.remarks.contains(keywordFilter)) {
|
||||||
|
serversCache.add(ServersCache(guid, config))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testAllTcping() {
|
fun testAllTcping() {
|
||||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||||
Utils.closeAllTcpSockets()
|
SpeedtestUtil.closeAllTcpSockets()
|
||||||
MmkvManager.clearAllTestDelayResults()
|
MmkvManager.clearAllTestDelayResults()
|
||||||
updateListAction.value = -1 // update all
|
updateListAction.value = -1 // update all
|
||||||
|
|
||||||
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||||
for (guid in serverList) {
|
for (item in serversCache) {
|
||||||
serversCache.getOrElse(guid, { MmkvManager.decodeServerConfig(guid) })?.getProxyOutbound()?.let { outbound ->
|
item.config.getProxyOutbound()?.let { outbound ->
|
||||||
val serverAddress = outbound.getServerAddress()
|
val serverAddress = outbound.getServerAddress()
|
||||||
val serverPort = outbound.getServerPort()
|
val serverPort = outbound.getServerPort()
|
||||||
if (serverAddress != null && serverPort != null) {
|
if (serverAddress != null && serverPort != null) {
|
||||||
tcpingTestScope.launch {
|
tcpingTestScope.launch {
|
||||||
val testResult = Utils.tcping(serverAddress, serverPort)
|
val testResult = SpeedtestUtil.tcping(serverAddress, serverPort)
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
MmkvManager.encodeServerTestDelayMillis(guid, testResult)
|
MmkvManager.encodeServerTestDelayMillis(item.guid, testResult)
|
||||||
updateListAction.value = serverList.indexOf(guid)
|
updateListAction.value = getPosition(item.guid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,14 +120,82 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testAllRealPing() {
|
||||||
|
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG_CANCEL, "")
|
||||||
|
MmkvManager.clearAllTestDelayResults()
|
||||||
|
updateListAction.value = -1 // update all
|
||||||
|
|
||||||
|
getApplication<AngApplication>().toast(R.string.connection_test_testing)
|
||||||
|
viewModelScope.launch(Dispatchers.Default) { // without Dispatchers.Default viewModelScope will launch in main thread
|
||||||
|
for (item in serversCache) {
|
||||||
|
val config = V2rayConfigUtil.getV2rayConfig(getApplication(), item.guid)
|
||||||
|
if (config.status) {
|
||||||
|
MessageUtil.sendMsg2TestService(getApplication(), AppConfig.MSG_MEASURE_CONFIG, Pair(item.guid, config.content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun testCurrentServerRealPing() {
|
fun testCurrentServerRealPing() {
|
||||||
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_MEASURE_DELAY, "")
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
}
|
||||||
val result = Utils.testConnection(getApplication(), socksPort)
|
|
||||||
launch(Dispatchers.Main) {
|
fun filterConfig(context :Context) {
|
||||||
updateTestResultAction.value = result
|
val subscriptions = MmkvManager.decodeSubscriptions()
|
||||||
|
val listId = subscriptions.map { it.first }.toList().toMutableList()
|
||||||
|
val listRemarks = subscriptions.map { it.second.remarks }.toList().toMutableList()
|
||||||
|
listRemarks += context.getString(R.string.filter_config_all)
|
||||||
|
val checkedItem = if (subscriptionId.isNotEmpty()) {
|
||||||
|
listId.indexOf(subscriptionId)
|
||||||
|
} else {
|
||||||
|
listRemarks.count() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val ivBinding = DialogConfigFilterBinding.inflate(LayoutInflater.from(context))
|
||||||
|
ivBinding.spSubscriptionId.adapter = ArrayAdapter<String>( context, android.R.layout.simple_spinner_dropdown_item, listRemarks)
|
||||||
|
ivBinding.spSubscriptionId.setSelection(checkedItem)
|
||||||
|
ivBinding.etKeyword.text = Utils.getEditable(keywordFilter)
|
||||||
|
val builder = AlertDialog.Builder(context).setView(ivBinding.root)
|
||||||
|
builder.setTitle(R.string.title_filter_config)
|
||||||
|
builder.setPositiveButton(R.string.tasker_setting_confirm) { dialogInterface: DialogInterface?, _: Int ->
|
||||||
|
try {
|
||||||
|
val position = ivBinding.spSubscriptionId.selectedItemPosition
|
||||||
|
subscriptionId = if (listRemarks.count() - 1 == position) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
subscriptions[position].first
|
||||||
|
}
|
||||||
|
keywordFilter = ivBinding.etKeyword.text.toString()
|
||||||
|
reloadServerList()
|
||||||
|
|
||||||
|
dialogInterface?.dismiss()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
builder.show()
|
||||||
|
// AlertDialog.Builder(context)
|
||||||
|
// .setSingleChoiceItems(listRemarks.toTypedArray(), checkedItem) { dialog, i ->
|
||||||
|
// try {
|
||||||
|
// subscriptionId = if (listRemarks.count() - 1 == i) {
|
||||||
|
// ""
|
||||||
|
// } else {
|
||||||
|
// subscriptions[i].first
|
||||||
|
// }
|
||||||
|
// reloadServerList()
|
||||||
|
// dialog.dismiss()
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// e.printStackTrace()
|
||||||
|
// }
|
||||||
|
// }.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPosition(guid: String) : Int {
|
||||||
|
serversCache.forEachIndexed { index, it ->
|
||||||
|
if (it.guid == guid)
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mMsgReceiver = object : BroadcastReceiver() {
|
private val mMsgReceiver = object : BroadcastReceiver() {
|
||||||
@@ -141,6 +218,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||||
isRunning.value = false
|
isRunning.value = false
|
||||||
}
|
}
|
||||||
|
AppConfig.MSG_MEASURE_DELAY_SUCCESS -> {
|
||||||
|
updateTestResultAction.value = intent.getStringExtra("content")
|
||||||
|
}
|
||||||
|
AppConfig.MSG_MEASURE_CONFIG_SUCCESS -> {
|
||||||
|
val resultPair = intent.getSerializableExtra("content") as Pair<String, Long>
|
||||||
|
MmkvManager.encodeServerTestDelayMillis(resultPair.first, resultPair.second)
|
||||||
|
updateListAction.value = getPosition(resultPair.first)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.v2ray.ang.viewmodel
|
package com.v2ray.ang.viewmodel
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.tencent.mmkv.MMKV
|
import com.tencent.mmkv.MMKV
|
||||||
import com.v2ray.ang.AppConfig
|
import com.v2ray.ang.AppConfig
|
||||||
import com.v2ray.ang.util.MmkvManager
|
import com.v2ray.ang.util.MmkvManager
|
||||||
@@ -30,6 +30,11 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
AppConfig.PREF_VPN_DNS,
|
AppConfig.PREF_VPN_DNS,
|
||||||
AppConfig.PREF_REMOTE_DNS,
|
AppConfig.PREF_REMOTE_DNS,
|
||||||
AppConfig.PREF_DOMESTIC_DNS,
|
AppConfig.PREF_DOMESTIC_DNS,
|
||||||
|
AppConfig.PREF_LOCAL_DNS_PORT,
|
||||||
|
AppConfig.PREF_SOCKS_PORT,
|
||||||
|
AppConfig.PREF_HTTP_PORT,
|
||||||
|
AppConfig.PREF_LOGLEVEL,
|
||||||
|
AppConfig.PREF_LANGUAGE,
|
||||||
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
|
||||||
AppConfig.PREF_ROUTING_MODE,
|
AppConfig.PREF_ROUTING_MODE,
|
||||||
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
AppConfig.PREF_V2RAY_ROUTING_AGENT,
|
||||||
@@ -41,12 +46,14 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
AppConfig.PREF_PROXY_SHARING,
|
AppConfig.PREF_PROXY_SHARING,
|
||||||
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
AppConfig.PREF_LOCAL_DNS_ENABLED,
|
||||||
AppConfig.PREF_FAKE_DNS_ENABLED,
|
AppConfig.PREF_FAKE_DNS_ENABLED,
|
||||||
AppConfig.PREF_FORWARD_IPV6,
|
AppConfig.PREF_ALLOW_INSECURE,
|
||||||
|
AppConfig.PREF_PREFER_IPV6,
|
||||||
AppConfig.PREF_PER_APP_PROXY,
|
AppConfig.PREF_PER_APP_PROXY,
|
||||||
AppConfig.PREF_BYPASS_APPS, -> {
|
AppConfig.PREF_BYPASS_APPS,
|
||||||
|
AppConfig.PREF_CONFIRM_REMOVE, -> {
|
||||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
|
||||||
}
|
}
|
||||||
AppConfig.PREF_SNIFFING_ENABLED, -> {
|
AppConfig.PREF_SNIFFING_ENABLED -> {
|
||||||
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
|
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
|
||||||
}
|
}
|
||||||
AppConfig.PREF_PER_APP_PROXY_SET -> {
|
AppConfig.PREF_PER_APP_PROXY_SET -> {
|
||||||
|
|||||||
BIN
V2rayNG/app/src/main/res/drawable-hdpi/ic_stat_direct.png
Normal file
|
After Width: | Height: | Size: 964 B |
BIN
V2rayNG/app/src/main/res/drawable-hdpi/ic_stat_name.png
Normal file
|
After Width: | Height: | Size: 389 B |
BIN
V2rayNG/app/src/main/res/drawable-hdpi/ic_stat_proxy.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
V2rayNG/app/src/main/res/drawable-mdpi/ic_stat_direct.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
V2rayNG/app/src/main/res/drawable-mdpi/ic_stat_name.png
Normal file
|
After Width: | Height: | Size: 279 B |
BIN
V2rayNG/app/src/main/res/drawable-mdpi/ic_stat_proxy.png
Normal file
|
After Width: | Height: | Size: 882 B |
BIN
V2rayNG/app/src/main/res/drawable-xhdpi/ic_stat_direct.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
V2rayNG/app/src/main/res/drawable-xhdpi/ic_stat_name.png
Normal file
|
After Width: | Height: | Size: 494 B |
BIN
V2rayNG/app/src/main/res/drawable-xhdpi/ic_stat_proxy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
V2rayNG/app/src/main/res/drawable-xxhdpi/ic_stat_direct.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
V2rayNG/app/src/main/res/drawable-xxhdpi/ic_stat_name.png
Normal file
|
After Width: | Height: | Size: 698 B |
BIN
V2rayNG/app/src/main/res/drawable-xxhdpi/ic_stat_proxy.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
V2rayNG/app/src/main/res/drawable-xxxhdpi/ic_stat_name.png
Normal file
|
After Width: | Height: | Size: 867 B |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:state_pressed="true"
|
|
||||||
android:drawable="@color/secondary_text" />
|
|
||||||
<item android:state_focused="true"
|
|
||||||
android:drawable="@color/secondary_text" />
|
|
||||||
<item android:state_hovered="true"
|
|
||||||
android:drawable="@color/secondary_text" />
|
|
||||||
<item android:drawable="@color/colorPrimary_text" />
|
|
||||||
</selector>
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM17,13l-5,5 -5,-5h3V9h4v4h3z" />
|
||||||
|
</vector>
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
34
V2rayNG/app/src/main/res/drawable/ic_file_white_24dp.xml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M11,23h8a3,3 0,0 0,3 -3V8L15,1H7A3,3 0,0 0,4 4V9"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFF"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M15,5a3,3 0,0 0,3 3h4L15,1Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFF"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M2,17L10,17"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFF"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M6,13L6,21"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#FFF"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||