Compare commits

...

62 Commits

Author SHA1 Message Date
2dust
4a1c62a67c Merge pull request #787 from yuhan6665/fix-selected
Fix update full config when settings change
2020-12-06 13:48:03 +08:00
yuhan6665
c9a6a459d4 Fix update full config when settings change
Now daemon process does not reference the node list at all and
only depend on a couple of settings like PREF_CURR_CONFIG..
2020-12-05 23:42:44 -05:00
2dust
21fdcf4ccf Merge pull request #783 from yuhan6665/fix-selected
Fix select node of two processes in sync
2020-12-05 18:20:11 +08:00
yuhan6665
7c7a623ae5 Fix select node of two processes in sync
As proxy only mode is added in 1.4.0, I moved the toggle components to the daemon process
When user start service from toggle, the full config is generated from a cached list.
However, this cache is not in sync with the main process list.
In fact, we don't need to generate full config right before the service start. We only
need to when active node is changed.
This way, code logic and daemon process is kept simple
2020-12-04 22:37:31 -05:00
2dust
b3074e9697 Merge pull request #763 from yuhan6665/memory-optimization
Slightly improve memory by reduce unnecessary DPreference usage
2020-11-29 09:31:54 +08:00
yuhan6665
513ebcfa23 Slightly improve memory by reduce unnecessary DPreference usage
See more details in _Ext.kt.
In the future, change will be made to our config storage, so that
the service started through TileService/Widget/ScSwitchActivity will
also not launch main process. That will greatly reduce memory usage
2020-11-28 18:08:50 -05:00
2dust
50d9057f1a Merge pull request #750 from yuhan6665/viewmodel-test
Add ViewModel
2020-11-16 13:22:08 +08:00
2dust
2a563e7884 Merge pull request #747 from liyufan/new_template
Use new issue template
2020-11-16 13:21:40 +08:00
2dust
c69cd18842 Merge pull request #732 from rurirei/isTun2socksRunningi
isTun2socksRunning bool
2020-11-16 13:20:48 +08:00
2dust
7f2ced85a8 Merge pull request #730 from rurirei/stoploop
stoploop update
2020-11-16 13:18:46 +08:00
yuhan6665
6c5eef99b5 Move tests and broadcast listener to ViewModel 2020-11-15 18:37:46 -05:00
yuhan6665
d7c3bae8cc Add MainViewModel
ViewModel is the recommend approach for asynchronous loading
for Activity.
The variable stays even if the Activity is killed temporarily.
2020-11-15 18:37:46 -05:00
LYF
57c98f7c50 Use new issue template 2020-11-13 22:21:35 +08:00
rurirei
49be23c56a bool in return 2020-11-08 11:40:02 +08:00
rurirei
9b658e9a22 IsTun2socksRunning bool 2020-11-08 11:34:49 +08:00
rurirei
aaa84d081f stoploop update 2020-11-08 10:53:23 +08:00
2dust
5628cbee3a Merge pull request #725 from mzz2017/patch-1
fix: the problem that importing SS URL not support standard format
2020-11-05 14:30:56 +08:00
2dust
3dd663d927 Merge pull request #698 from yuhan6665/master
Release AndroidLibV2rayLite 22
2020-11-05 14:30:41 +08:00
mzz
9e49a2dbd9 fix: the problem that importing SS URL not support standard format 2020-11-02 12:37:26 +08:00
2dust
6c199c1687 Merge pull request #662 from yuhan6665/ext-geo
Add external geofile replacement help
2020-10-24 13:35:21 +08:00
yuhan6665
39af5fdb86 Release AndroidLibV2rayLite 22 2020-10-21 21:31:23 -04:00
2dust
7b2794f6be Merge pull request #695 from rurirei/proxyOnly
proxyOnly update
2020-10-22 07:59:19 +08:00
2dust
411d9e5c9a Merge pull request #691 from rurirei/api30
target 30
2020-10-22 07:58:41 +08:00
Rurirei
a57aee9424 proxyOnly update
* do not run Tun2socks under proxyOnly
2020-10-21 15:19:55 +08:00
Rurirei
4602afc67e call stopV2ray on onDestroy 2020-10-21 15:14:44 +08:00
Rurirei
ceb29840f2 BuildTools 30 fix 2020-10-20 09:49:16 +08:00
Rurirei
1c1f130ca7 update badge 2020-10-20 09:42:48 +08:00
Rurirei
afa0eb9375 package visibility 2020-10-20 07:20:37 +08:00
Rurirei
49b682f0f3 target 30
* update dependencies
2020-10-20 07:10:19 +08:00
Rurirei
d5def3bf2f useless anko 2020-10-20 07:06:36 +08:00
2dust
51b97b64f2 Merge pull request #670 from yuhan6665/fix-gson
Fix crash when custom config cannot be parsed by Gson
2020-10-15 08:18:59 +08:00
2dust
3af9ce1a1a Merge pull request #657 from rurirei/native
native fixes
2020-10-15 08:18:48 +08:00
yuhan6665
a5d3dda941 Fix crash when custom config cannot be parsed by Gson 2020-10-14 11:28:32 -04:00
yuhan6665
71a3e300d8 Add external geofile replacement help 2020-10-09 21:34:15 -04:00
Rurirei
030b9a3900 native fixes
* call sendFd() before tun2socks start
2020-10-08 15:50:51 +08:00
Rurirei
24d105a53c catch logs to checkout this error 2020-10-08 15:49:30 +08:00
2dust
45805d6df7 Merge pull request #644 from yuhan6665/clean-dependency
Clean dependency
2020-10-04 19:16:27 +08:00
2dust
4437e6699b Merge pull request #635 from rurirei/diff
issues of VPNService.kt
2020-10-04 19:16:14 +08:00
2dust
fa409f91e4 Merge pull request #626 from yuhan6665/go
Clean up tun2socks
2020-10-04 19:16:00 +08:00
yuhan6665
89be5f077b Say goodbye to Anko
replace with support lib PreferenceManager
replace LayoutInflater
2020-10-03 11:19:46 -04:00
yuhan6665
896889778f Replace Anko with support lib AlertDialog
replace some property setter
2020-10-03 11:19:46 -04:00
yuhan6665
10f705a8b2 Use standard startActivity 2020-10-03 11:19:46 -04:00
yuhan6665
2956fa2030 Replace doasync with Kotlin coroutine
Using a custom coroutine scope, we don't need to keep track of the
running job. We can simply call cancel children for the scope.
2020-10-03 11:19:46 -04:00
yuhan6665
c2a704a6ea Deprecate some anko function
- Replace toast with inline function
- Use standard startActivityForResult
2020-10-03 11:19:46 -04:00
yuhan6665
78cac0cd90 Replace divider with standard library
Also, cleanup some unused classes
2020-10-03 11:19:46 -04:00
Rurirei
3ffb2e8e05 catch exception for lateinit
PropertyNotInitialized exception needs catch for lateinit var
2020-10-03 20:23:43 +08:00
Rurirei
e2d667e0bb sendFd() should not called twice 2020-10-01 17:36:07 +08:00
Rurirei
3ae0777d7f useless bool 2020-10-01 17:07:51 +08:00
Rurirei
5e6348676c ignore err of registerNetworkConnectivity 2020-10-01 15:32:08 +08:00
2dust
df3f1ca3ef Merge pull request #620 from yuhan6665/up-version
Update dependencies
2020-09-29 08:05:46 +08:00
yuhan6665
94f2bec329 Clean up tun2socks 2020-09-27 23:17:47 -04:00
yuhan6665
c54d8fa43a Update dependencies
Fix a delegate annotation for Kotlin 1.4
2020-09-25 18:37:29 -04:00
2dust
5bbbdcf6f2 Merge pull request #610 from yuhan6665/fix-protect
Fix protect() when there is no service
2020-09-21 07:59:55 +08:00
yuhan6665
4abf20fa32 Fix protect() when there is no service 2020-09-20 19:00:14 -04:00
2dust
723727feb9 Merge pull request #607 from yuhan6665/help-proxy-mode
Help proxy mode
2020-09-19 18:56:15 +08:00
yuhan6665
2efd4b741c Add badge, wiki link and dev guide 2020-09-18 17:48:54 -04:00
yuhan6665
83aab0f880 Add help link in mode setting 2020-09-18 17:48:54 -04:00
2dust
66ea17877e Merge pull request #598 from yuhan6665/fix-process
Add missing process for ScSwitchActivity
2020-09-12 19:48:04 +08:00
2dust
26bc985368 Merge pull request #597 from yuhan6665/settings-ui
Settings ui
2020-09-12 19:47:48 +08:00
yuhan6665
5bbf40c784 Grey out per-app proxy in proxy only mode 2020-09-11 21:30:08 -04:00
yuhan6665
6d5c23245c Move version to drawer
Make it easier to check version.
Technically, version should not be selectable setting
2020-09-11 21:30:08 -04:00
yuhan6665
b148290211 Add missing process for ScSwitchActivity 2020-09-11 21:29:58 -04:00
62 changed files with 702 additions and 688 deletions

View File

@@ -1,3 +1,8 @@
---
name: v2rayNG程序问题
about: 创建一个报告来帮助我们改进
---
在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。 在提出问题前请先自行排除服务器端问题,同时也请通过搜索确认是否有人提出过相同问题。
@@ -14,7 +19,8 @@
### 日志信息 ### 日志信息
<details> <details>
通过 `adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main` 获取日志。请自行删减日志中可能出现的敏感信息。
通过`adb logcat -s com.v2ray.ang GoLog V2rayConfigUtilGoLog Main`获取日志。请自行删减日志中可能出现的敏感信息。
如果问题可重现,建议先执行`adb logcat -c`清空系统日志再执行上述命令,再操作重现问题。 如果问题可重现,建议先执行`adb logcat -c`清空系统日志再执行上述命令,再操作重现问题。
``` ```

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: V2Ray程序问题
url: https://github.com/v2fly/v2ray-core/
about: 如果您有V2Ray而非v2rayNG的问题请至这个链接讨论。

View File

@@ -6,6 +6,7 @@ import (
type Status struct { type Status struct {
IsRunning bool IsRunning bool
IsTRunning bool
PackageName string PackageName string
PackageCodePath string PackageCodePath string
@@ -13,7 +14,7 @@ type Status struct {
} }
func CheckVersion() int { func CheckVersion() int {
return 21 return 22
} }
func (v *Status) GetDataDir() string { func (v *Status) GetDataDir() string {

View File

@@ -8,9 +8,6 @@ asset:
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat # cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geosite.dat > geosite.dat
# cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat # cd assets;curl https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/data/geoip.dat > geoip.dat
shippedBinary:
cd shippedBinarys; $(MAKE) shippedBinary
fetchDep: fetchDep:
-go get github.com/2dust/AndroidLibV2rayLite -go get github.com/2dust/AndroidLibV2rayLite
go get github.com/2dust/AndroidLibV2rayLite go get github.com/2dust/AndroidLibV2rayLite
@@ -30,5 +27,5 @@ downloadGoMobile:
BuildMobile: BuildMobile:
@echo Stub @echo Stub
all: asset pb shippedBinary fetchDep all: asset pb fetchDep
@echo DONE @echo DONE

View File

@@ -3,14 +3,13 @@ package Escort
import ( import (
"os" "os"
"os/exec" "os/exec"
"time"
"log" "log"
"github.com/2dust/AndroidLibV2rayLite/CoreI" "github.com/2dust/AndroidLibV2rayLite/CoreI"
) )
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, sendFd func() int) { func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string) {
log.Println(proc, pt) log.Println(proc, pt)
count := 0 count := 0
for count <= 42 { for count <= 42 {
@@ -37,13 +36,6 @@ func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, se
*v.escortProcess = append(*v.escortProcess, cmd.Process) *v.escortProcess = append(*v.escortProcess, cmd.Process)
log.Println("EscortRun Waiting....") log.Println("EscortRun Waiting....")
if count > 0 {
go func() {
time.Sleep(time.Second)
sendFd()
}()
}
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Println("EscortRun cmd.Wait err:", err) log.Println("EscortRun cmd.Wait err:", err)
} }

View File

@@ -34,14 +34,10 @@ $NDK_HOME/ndk-build \
NDK_OUT=$TMPDIR/tmp \ NDK_OUT=$TMPDIR/tmp \
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4 APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/shippedBinarys/ArchDep/arm/ install -v -m755 libs/armeabi-v7a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
install -v -m755 libs/arm64-v8a/tun2socks $__dir/shippedBinarys/ArchDep/arm64/ install -v -m755 libs/arm64-v8a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
install -v -m755 libs/x86/tun2socks $__dir/shippedBinarys/ArchDep/386/ install -v -m755 libs/x86/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
install -v -m755 libs/x86_64/tun2socks $__dir/shippedBinarys/ArchDep/amd64/ install -v -m755 libs/x86_64/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
popd popd
pushd $__dir/shippedBinarys rm -rf $TMPDIR
make clean && make shippedBinary
popd
rm -rf $TMPDIR

View File

@@ -11,7 +11,6 @@ import (
"github.com/2dust/AndroidLibV2rayLite/CoreI" "github.com/2dust/AndroidLibV2rayLite/CoreI"
"github.com/2dust/AndroidLibV2rayLite/Process/Escort" "github.com/2dust/AndroidLibV2rayLite/Process/Escort"
"github.com/2dust/AndroidLibV2rayLite/VPN" "github.com/2dust/AndroidLibV2rayLite/VPN"
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
mobasset "golang.org/x/mobile/asset" mobasset "golang.org/x/mobile/asset"
v2core "v2ray.com/core" v2core "v2ray.com/core"
@@ -49,6 +48,7 @@ type V2RayPoint struct {
ConfigureFileContent string ConfigureFileContent string
EnableLocalDNS bool EnableLocalDNS bool
ForwardIpv6 bool ForwardIpv6 bool
ProxyOnly bool
} }
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/ /*V2RayVPNServiceSupportsSet To support Android VPN mode*/
@@ -58,7 +58,6 @@ type V2RayVPNServiceSupportsSet interface {
Shutdown() int Shutdown() int
Protect(int) int Protect(int) int
OnEmitStatus(int, string) int OnEmitStatus(int, string) int
SendFd() int
} }
/*RunLoop Run V2Ray main loop /*RunLoop Run V2Ray main loop
@@ -82,7 +81,6 @@ func (v *V2RayPoint) RunLoop() (err error) {
if !v.dialer.IsVServerReady() { if !v.dialer.IsVServerReady() {
log.Println("vServer cannot resolved, shutdown") log.Println("vServer cannot resolved, shutdown")
v.StopLoop() v.StopLoop()
v.SupportSet.Shutdown()
} }
// stop waiting if manually closed // stop waiting if manually closed
@@ -101,9 +99,7 @@ func (v *V2RayPoint) StopLoop() (err error) {
v.v2rayOP.Lock() v.v2rayOP.Lock()
defer v.v2rayOP.Unlock() defer v.v2rayOP.Unlock()
if v.status.IsRunning { if v.status.IsRunning {
close(v.closeChan)
v.shutdownInit() v.shutdownInit()
v.SupportSet.OnEmitStatus(0, "Closed")
} }
return return
} }
@@ -113,6 +109,10 @@ func (v *V2RayPoint) GetIsRunning() bool {
return v.status.IsRunning return v.status.IsRunning
} }
func (v *V2RayPoint) GetIsTRunning() bool {
return v.status.IsTRunning
}
//Delegate Funcation //Delegate Funcation
func (v V2RayPoint) QueryStats(tag string, direct string) int64 { func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
if v.statsManager == nil { if v.statsManager == nil {
@@ -126,24 +126,19 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
} }
func (v *V2RayPoint) shutdownInit() { func (v *V2RayPoint) shutdownInit() {
v.status.IsRunning = false close(v.closeChan)
v.statsManager = nil
v.status.Vpoint.Close() v.status.Vpoint.Close()
v.status.Vpoint = nil v.status.Vpoint = nil
v.statsManager = nil v.status.IsRunning = false
v.escorter.EscortingDown() v.escorter.EscortingDown()
v.SupportSet.Shutdown()
v.SupportSet.OnEmitStatus(0, "Closed")
} }
func (v *V2RayPoint) pointloop() error { func (v *V2RayPoint) pointloop() error {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
log.Println("loading v2ray config") log.Println("loading v2ray config")
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent)) config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
if err != nil { if err != nil {
@@ -171,6 +166,21 @@ func (v *V2RayPoint) pointloop() error {
v.SupportSet.Prepare() v.SupportSet.Prepare()
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6)) v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
v.SupportSet.OnEmitStatus(0, "Running") v.SupportSet.OnEmitStatus(0, "Running")
v.status.IsTRunning = false
if !v.ProxyOnly {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
v.status.IsTRunning = true
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
}
return nil return nil
} }
@@ -227,17 +237,10 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
} }
func (v V2RayPoint) runTun2socks() error { func (v V2RayPoint) runTun2socks() error {
shipb := shippedBinarys.FirstRun{Status: v.status}
if err := shipb.CheckAndExport(); err != nil {
log.Println(err)
return err
}
v.escorter.EscortingUp() v.escorter.EscortingUp()
go v.escorter.EscortRun( go v.escorter.EscortRun(
v.status.GetApp("libtun2socks.so"), v.status.GetApp("libtun2socks.so"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "", v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
v.SupportSet.SendFd)
return nil return nil
} }

View File

@@ -1,13 +0,0 @@
Platdep=shippedBinary.386 shippedBinary.amd64 shippedBinary.arm64 shippedBinary.arm
shippedBinaryDep:
go get -u github.com/jteeuwen/go-bindata/...
shippedBinary.%:
go-bindata -nometadata -nomemcopy -pkg shippedBinarys -o ./binary_$*.go -tags $* ArchIndep/ ArchDep/$*/
shippedBinary:shippedBinaryDep $(Platdep)
@echo "Done"
clean:
-rm binary*

View File

@@ -1,69 +0,0 @@
package shippedBinarys
import (
"log"
"os"
"strconv"
"github.com/2dust/AndroidLibV2rayLite/CoreI"
)
type FirstRun struct {
Status *CoreI.Status
}
func (v *FirstRun) checkIfRcExist() error {
datadir := v.Status.GetDataDir()
if _, err := os.Stat(datadir + strconv.Itoa(CoreI.CheckVersion())); !os.IsNotExist(err) {
log.Println("file exists")
return nil
}
IndepDir, err := AssetDir("ArchIndep")
log.Println(IndepDir)
if err != nil {
return err
}
for _, fn := range IndepDir {
log.Println(datadir+"ArchIndep/"+fn)
err := RestoreAsset(datadir, "ArchIndep/"+fn)
log.Println(err)
//GrantPremission
os.Chmod(datadir+"ArchIndep/"+fn, 0700)
log.Println(os.Remove(datadir + fn))
log.Println(os.Symlink(datadir+"ArchIndep/"+fn, datadir + fn))
}
DepDir, err := AssetDir("ArchDep")
log.Println(DepDir)
if err != nil {
return err
}
for _, fn := range DepDir {
DepDir2, err := AssetDir("ArchDep/" + fn)
log.Println("ArchDep/" + fn)
if err != nil {
return err
}
for _, FND := range DepDir2 {
log.Println(datadir+"ArchDep/"+fn+"/"+FND)
RestoreAsset(datadir, "ArchDep/"+fn+"/"+FND)
os.Chmod(datadir+"ArchDep/"+fn+"/"+FND, 0700)
log.Println(os.Remove(datadir + FND))
log.Println(os.Symlink(datadir+"ArchDep/"+fn+"/"+FND, datadir+FND))
}
}
s, _ := os.Create(datadir + strconv.Itoa(CoreI.CheckVersion()))
s.Close()
return nil
}
func (v *FirstRun) CheckAndExport() error {
return v.checkIfRcExist()
}

View File

@@ -1,5 +1,34 @@
# v2rayNG # v2rayNG
A V2Ray client for Android
[![API](https://img.shields.io/badge/API-17%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/jelly-bean#android-4.2)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.4.10-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang"> <a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" /> <img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />
</a> </a>
### Usage
#### Geoip and Geosite
v2rayNG release already embedded domain file `geoip.dat` and `geosite.dat`. However it is (probably) not the latest and not the most complete list.
For power user, the embedded files can be easily replaced with the following steps:
1. Launch v2rayNG (v1.4.9+)
2. Find existing geoip.dat and geosite.dat in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
3. Replace them with the latest [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip)
4. Enhanced version can be found in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (recommend to use `geosite:geolocation-!cn` for proxy dns and routing)
5. It is also possible to use third party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
#### See more in our [wiki](https://github.com/2dust/v2rayNG/wiki)
### Development guide
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
The aar can be compiled from the Golang project under AndroidLibV2rayLite folder. For a quick start, read guide for [Go Mobile](https://github.com/golang/go/wiki/Mobile)
and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)
v2rayNG can run on Android Emulators, with minimum Android 5.0

View File

@@ -38,6 +38,10 @@ android {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
splits { splits {
abi { abi {
enable true enable true
@@ -62,14 +66,17 @@ android {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support.constraint:constraint-layout:1.1.3' testImplementation 'junit:junit:4.13'
testImplementation 'junit:junit:4.12'
implementation project(':dpreference') implementation project(':dpreference')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2" // 1.3.x has compile error: implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
// More than one file was found with OS independent path 'META-INF/proguard/coroutines.pro'
// Androidx ktx
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:livedata:1.1.1'
// Android support library // Android support library
implementation "com.android.support:support-v4:$supportLibVersion" implementation "com.android.support:support-v4:$supportLibVersion"
@@ -79,22 +86,16 @@ dependencies {
implementation "com.android.support:preference-v7:$supportLibVersion" implementation "com.android.support:preference-v7:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion" implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:multidex:1.0.3" implementation "com.android.support:multidex:1.0.3"
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
// DSL // DSL
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion" implementation 'com.google.code.gson:gson:2.8.6'
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'io.reactivex:rxjava:1.3.4' implementation 'io.reactivex:rxjava:1.3.4'
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar' implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
implementation 'io.reactivex:rxkotlin:0.60.0'
implementation 'me.dm7.barcodescanner:core:1.9.8' implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar' implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'com.beust:klaxon:3.0.1'
implementation 'com.android.support:multidex:1.0.3'
implementation(name: 'libv2ray', ext: 'aar') implementation(name: 'libv2ray', ext: 'aar')
//implementation(name: 'tun2socks', ext: 'aar') //implementation(name: 'tun2socks', ext: 'aar')

Binary file not shown.

View File

@@ -44,9 +44,6 @@
static void throwUninitializedPropertyAccessException(java.lang.String); static void throwUninitializedPropertyAccessException(java.lang.String);
} }
-dontwarn org.jetbrains.anko.internals.**
-keep class org.jetbrains.anko.internals.** { *;}
-dontwarn rx.internal.util.unsafe.** -dontwarn rx.internal.util.unsafe.**
-keep class rx.internal.util.unsafe.** { *;} -keep class rx.internal.util.unsafe.** { *;}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.v2ray.ang"> package="com.v2ray.ang">
<supports-screens <supports-screens
@@ -12,6 +13,11 @@
<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 -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@@ -21,12 +27,14 @@
<uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> --> <!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<application <application
android:name=".AngApplication" android:name=".AngApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:extractNativeLibs="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity <activity
android:name=".ui.MainActivity" android:name=".ui.MainActivity"
@@ -34,20 +42,16 @@
android:launchMode="singleTask"> android:launchMode="singleTask">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter> </intent-filter>
<meta-data <meta-data
android:name="android.app.shortcuts" android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" /> android:resource="@xml/shortcuts" />
@@ -79,6 +83,7 @@
<activity <activity
android:name=".ui.ScSwitchActivity" android:name=".ui.ScSwitchActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:process=":RunSoLibV2RayDaemon"
android:theme="@style/AppTheme.NoActionBar.Translucent" /> android:theme="@style/AppTheme.NoActionBar.Translucent" />
<service <service

View File

@@ -1,10 +1,9 @@
package com.v2ray.ang package com.v2ray.ang
//import com.squareup.leakcanary.LeakCanary
import android.support.multidex.MultiDexApplication import android.support.multidex.MultiDexApplication
import android.support.v7.preference.PreferenceManager
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import me.dozen.dpreference.DPreference import me.dozen.dpreference.DPreference
import org.jetbrains.anko.defaultSharedPreferences
class AngApplication : MultiDexApplication() { class AngApplication : MultiDexApplication() {
companion object { companion object {
@@ -22,6 +21,7 @@ class AngApplication : MultiDexApplication() {
// LeakCanary.install(this) // LeakCanary.install(this)
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
if (firstRun) if (firstRun)
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply() defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()

View File

@@ -38,6 +38,7 @@ object AppConfig {
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt" const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/" const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues" const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
const val promotionUrl = "https://1.2345345.xyz/ads.html" const val promotionUrl = "https://1.2345345.xyz/ads.html"
const val DNS_AGENT = "1.1.1.1" const val DNS_AGENT = "1.1.1.1"

View File

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

View File

@@ -21,7 +21,7 @@ class TaskerReceiver : BroadcastReceiver() {
return return
} else if (switch) { } else if (switch) {
if (guid == AppConfig.TASKER_DEFAULT_GUID) { if (guid == AppConfig.TASKER_DEFAULT_GUID) {
Utils.startVService(context) Utils.startVServiceFromToggle(context)
} else { } else {
Utils.startVService(context, guid) Utils.startVService(context, guid)
} }

View File

@@ -11,12 +11,10 @@ import android.service.quicksettings.Tile
import android.service.quicksettings.TileService import android.service.quicksettings.TileService
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
@TargetApi(Build.VERSION_CODES.N) @TargetApi(Build.VERSION_CODES.N)
class QSTileService : TileService() { class QSTileService : TileService() {
@@ -27,7 +25,7 @@ class QSTileService : TileService() {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle) qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
} else if (state == Tile.STATE_ACTIVE) { } else if (state == Tile.STATE_ACTIVE) {
qsTile?.state = Tile.STATE_ACTIVE qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG") qsTile?.label = V2RayServiceManager.currentConfigName
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v) qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
} }

View File

@@ -11,5 +11,4 @@ interface ServiceControl {
fun vpnProtect(socket: Int): Boolean fun vpnProtect(socket: Int): Boolean
fun vpnSendFd()
} }

View File

@@ -37,10 +37,6 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
return true return true
} }
override fun vpnSendFd() {
// do nothing
}
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
return null return null
} }

View File

@@ -18,6 +18,8 @@ import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
@@ -50,14 +52,20 @@ object V2RayServiceManager {
Seq.setContext(context) Seq.setContext(context)
} }
} }
var currentConfigName = "NG"
private var lastQueryTime = 0L private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context, mode: String) { fun startV2Ray(context: Context) {
val intent = if (mode == "VPN") { if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{
context.toast(R.string.toast_services_start)
}
val intent = if (context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
Intent(context.applicationContext, V2RayVpnService::class.java) Intent(context.applicationContext, V2RayVpnService::class.java)
} else { } else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java) Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -88,7 +96,7 @@ object V2RayServiceManager {
} }
override fun protect(l: Long): Long { override fun protect(l: Long): Long {
val serviceControl = serviceControl?.get() ?: return 1 val serviceControl = serviceControl?.get() ?: return 0
return if (serviceControl.vpnProtect(l.toInt())) 0 else 1 return if (serviceControl.vpnProtect(l.toInt())) 0 else 1
} }
@@ -111,16 +119,6 @@ object V2RayServiceManager {
} }
} }
override fun sendFd(): Long {
val serviceControl = serviceControl?.get() ?: return -1
try {
serviceControl.vpnSendFd()
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
return -1
}
return 0
}
} }
fun startV2rayPoint() { fun startV2rayPoint() {
@@ -141,6 +139,8 @@ object V2RayServiceManager {
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false) v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false) v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "") v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN"
currentConfigName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
try { try {
v2rayPoint.runLoop() v2rayPoint.runLoop()
@@ -244,7 +244,7 @@ object V2RayServiceManager {
mBuilder = NotificationCompat.Builder(service, channelId) mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_v) .setSmallIcon(R.drawable.ic_v)
.setContentTitle(service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentTitle(currentConfigName)
.setPriority(NotificationCompat.PRIORITY_MIN) .setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true) .setOngoing(true)
.setShowWhen(false) .setShowWhen(false)
@@ -355,13 +355,10 @@ object V2RayServiceManager {
} }
fun stopSpeedNotification() { fun stopSpeedNotification() {
val service = serviceControl?.get()?.getService() ?: return
if (mSubscription != null) { if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats mSubscription?.unsubscribe() //stop queryStats
mSubscription = null mSubscription = null
updateNotification(currentConfigName, 0, 0)
val cfName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cfName, 0, 0)
} }
} }
} }

View File

@@ -10,13 +10,14 @@ import android.os.ParcelFileDescriptor
import android.os.StrictMode import android.os.StrictMode
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.util.Log import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.ui.PerAppProxyActivity import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import org.jetbrains.anko.doAsync import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
@@ -32,7 +33,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
* *
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887 * Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/ */
private val defaultNetworkRequest by lazy @RequiresApi(Build.VERSION_CODES.P) { @delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkRequest by lazy {
NetworkRequest.Builder() NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
@@ -41,12 +43,13 @@ class V2RayVpnService : VpnService(), ServiceControl {
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
private val defaultNetworkCallback by lazy @RequiresApi(Build.VERSION_CODES.P) { @delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkCallback by lazy {
object : ConnectivityManager.NetworkCallback() { object : ConnectivityManager.NetworkCallback() {
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))
} }
@@ -56,8 +59,6 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
private var listeningForDefaultNetwork = false
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@@ -77,7 +78,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
V2RayServiceManager.cancelNotification() stopV2Ray()
} }
private fun setup(parameters: String) { private fun setup(parameters: String) {
@@ -125,7 +126,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) builder.setSession(V2RayServiceManager.currentConfigName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) { defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
@@ -151,8 +152,11 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback) try {
listeningForDefaultNetwork = true connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
} catch (e: Exception) {
e.printStackTrace()
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -160,7 +164,14 @@ 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.
mInterface = builder.establish() try {
mInterface = builder.establish()!!
} catch (e: Exception) {
// non-nullable lateinit var
e.printStackTrace()
stopV2Ray()
}
sendFd() sendFd()
} }
@@ -168,10 +179,10 @@ class V2RayVpnService : VpnService(), ServiceControl {
val fd = mInterface.fileDescriptor val fd = mInterface.fileDescriptor
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
doAsync { GlobalScope.launch(Dispatchers.IO) {
var tries = 0 var tries = 0
while (true) try { while (true) try {
Thread.sleep(50L shl tries) Thread.sleep(1000L shl tries)
Log.d(packageName, "sendFd tries: $tries") 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))
@@ -198,9 +209,12 @@ class V2RayVpnService : VpnService(), ServiceControl {
// val emptyInfo = VpnNetworkInfo() // val emptyInfo = VpnNetworkInfo()
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo) // val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
// saveVpnNetworkInfo(configName, info) // saveVpnNetworkInfo(configName, info)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && listeningForDefaultNetwork) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
connectivity.unregisterNetworkCallback(defaultNetworkCallback) try {
listeningForDefaultNetwork = false connectivity.unregisterNetworkCallback(defaultNetworkCallback)
} catch (ignored: Exception) {
// ignored
}
} }
V2RayServiceManager.stopV2rayPoint() V2RayServiceManager.stopV2rayPoint()
@@ -238,7 +252,4 @@ class V2RayVpnService : VpnService(), ServiceControl {
return protect(socket) return protect(socket)
} }
override fun vpnSendFd() {
sendFd()
}
} }

View File

@@ -13,11 +13,7 @@ import android.support.v7.widget.Toolbar
import android.util.Log import android.util.Log
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
//import com.v2ray.ang.InappBuyActivity
import com.v2ray.ang.R import com.v2ray.ang.R
import org.jetbrains.anko.startActivity
abstract class BaseDrawerActivity : BaseActivity() { abstract class BaseDrawerActivity : BaseActivity() {
companion object { companion object {
@@ -58,7 +54,7 @@ abstract class BaseDrawerActivity : BaseActivity() {
R.id.sub_setting -> activityClass = SubSettingActivity::class.java R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> { R.id.logcat -> {
startActivity<LogcatActivity>() startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
return return
} }
R.id.donate -> { R.id.donate -> {

View File

@@ -8,12 +8,12 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_logcat.* import kotlinx.android.synthetic.main.activity_logcat.*
import org.jetbrains.anko.doAsync import kotlinx.coroutines.Dispatchers
import org.jetbrains.anko.toast import kotlinx.coroutines.GlobalScope
import org.jetbrains.anko.uiThread import kotlinx.coroutines.launch
import java.io.IOException import java.io.IOException
import java.util.LinkedHashSet import java.util.LinkedHashSet
@@ -34,7 +34,7 @@ class LogcatActivity : BaseActivity() {
try { try {
pb_waiting.visibility = View.VISIBLE pb_waiting.visibility = View.VISIBLE
doAsync { GlobalScope.launch(Dispatchers.Default) {
if (shouldFlushLog) { if (shouldFlushLog) {
val lst = LinkedHashSet<String>() val lst = LinkedHashSet<String>()
lst.add("logcat") lst.add("logcat")
@@ -54,7 +54,7 @@ class LogcatActivity : BaseActivity() {
// InputStreamReader(process.inputStream)) // InputStreamReader(process.inputStream))
// val allText = bufferedReader.use(BufferedReader::readText) // val allText = bufferedReader.use(BufferedReader::readText)
val allText = process.inputStream.bufferedReader().use { it.readText() } val allText = process.inputStream.bufferedReader().use { it.readText() }
uiThread { launch(Dispatchers.Main) {
tv_logcat.text = allText tv_logcat.text = allText
tv_logcat.movementMethod = ScrollingMovementMethod() tv_logcat.movementMethod = ScrollingMovementMethod()
pb_waiting.visibility = View.GONE pb_waiting.visibility = View.GONE

View File

@@ -1,41 +1,41 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.content.* import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_main.*
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.V2rayConfigUtil
import org.jetbrains.anko.*
import java.lang.ref.SoftReference
import java.net.URL
import android.content.IntentFilter
import android.support.design.widget.NavigationView import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper
import android.text.TextUtils
import android.util.Log import android.util.Log
import com.v2ray.ang.dto.EConfigType import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
//import com.v2ray.ang.InappBuyActivity import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager.configs
import kotlinx.coroutines.*
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
companion object { companion object {
@@ -45,23 +45,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private const val REQUEST_SCAN_URL = 3 private const val REQUEST_SCAN_URL = 3
} }
var isRunning = false
set(value) {
field = value
adapter.changeable = !value
if (value) {
fab.imageResource = R.drawable.ic_v
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.imageResource = R.drawable.ic_v_idle
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
}
private val adapter by lazy { MainRecyclerAdapter(this) } private val adapter by lazy { MainRecyclerAdapter(this) }
private var mItemTouchHelper: ItemTouchHelper? = null private var mItemTouchHelper: ItemTouchHelper? = null
private val testingJobs = ArrayList<Job>() private val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -70,7 +56,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
fab.setOnClickListener { fab.setOnClickListener {
if (isRunning) { if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this) Utils.stopVService(this)
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") { } else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
@@ -84,16 +70,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
} }
layout_test.setOnClickListener { layout_test.setOnClickListener {
if (isRunning) { if (mainViewModel.isRunning.value == true) {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
tv_test_state.text = getString(R.string.connection_test_testing) tv_test_state.text = getString(R.string.connection_test_testing)
doAsync { mainViewModel.testCurrentServerRealPing()
val result = Utils.testConnection(this@MainActivity, socksPort)
uiThread {
tv_test_state.text = Utils.getEditable(result)
}
}
} else { } else {
// tv_test_state.text = getString(R.string.connection_test_fail) // tv_test_state.text = getString(R.string.connection_test_fail)
} }
@@ -113,6 +92,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
drawer_layout.addDrawerListener(toggle) drawer_layout.addDrawerListener(toggle)
toggle.syncState() toggle.syncState()
nav_view.setNavigationItemSelectedListener(this) nav_view.setNavigationItemSelectedListener(this)
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver()
}
private fun setupViewModelObserver() {
mainViewModel.updateListAction.observe(this, {
val index = it ?: return@observe
if (index >= 0) {
adapter.updateSelectedItem(index)
} else {
adapter.updateConfigList()
}
})
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
mainViewModel.isRunning.observe(this, {
val isRunning = it ?: return@observe
adapter.changeable = !isRunning
if (isRunning) {
fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
})
mainViewModel.startListenBroadcast()
} }
fun startV2Ray() { fun startV2Ray() {
@@ -121,32 +128,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
showCircle() showCircle()
// toast(R.string.toast_services_start) // toast(R.string.toast_services_start)
if (!Utils.startVService(this)) { if (!Utils.startVService(this, AngConfigManager.configs.index)) {
hideCircle() hideCircle()
} }
} }
override fun onStart() {
super.onStart()
isRunning = false
// val intent = Intent(this.applicationContext, V2RayVpnService::class.java)
// intent.`package` = AppConfig.ANG_PACKAGE
// bindService(intent, mConnection, BIND_AUTO_CREATE)
mMsgReceive = ReceiveMessageHandler(this@MainActivity)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onStop() {
super.onStop()
if (mMsgReceive != null) {
unregisterReceiver(mMsgReceive)
mMsgReceive = null
}
}
public override fun onResume() { public override fun onResume() {
super.onResume() super.onResume()
adapter.updateConfigList() adapter.updateConfigList()
@@ -185,6 +171,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true return true
} }
private fun getOptionIntent() = Intent().putExtra("position", -1)
.putExtra("isRunning", mainViewModel.isRunning.value == true)
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.import_qrcode -> { R.id.import_qrcode -> {
importQRcode(REQUEST_SCAN) importQRcode(REQUEST_SCAN)
@@ -195,17 +184,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true true
} }
R.id.import_manually_vmess -> { R.id.import_manually_vmess -> {
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning) startActivity(getOptionIntent().setClass(this, ServerActivity::class.java))
adapter.updateConfigList() adapter.updateConfigList()
true true
} }
R.id.import_manually_ss -> { R.id.import_manually_ss -> {
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning) startActivity(getOptionIntent().setClass(this, Server3Activity::class.java))
adapter.updateConfigList() adapter.updateConfigList()
true true
} }
R.id.import_manually_socks -> { R.id.import_manually_socks -> {
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning) startActivity(getOptionIntent().setClass(this, Server4Activity::class.java))
adapter.updateConfigList() adapter.updateConfigList()
true true
} }
@@ -246,35 +235,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
R.id.ping_all -> { R.id.ping_all -> {
testingJobs.forEach { mainViewModel.testAllTcping()
it.cancel()
}
testingJobs.clear()
Utils.closeAllTcpSockets()
for (k in 0 until configs.vmess.count()) {
configs.vmess[k].testResult = ""
adapter.updateConfigList()
}
for (k in 0 until configs.vmess.count()) {
var serverAddress = configs.vmess[k].address
var serverPort = configs.vmess[k].port
if (configs.vmess[k].configType == EConfigType.CUSTOM.value) {
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(applicationContext, configs.vmess[k].guid)
?: continue
serverAddress = serverOutbound.getServerAddress() ?: continue
serverPort = serverOutbound.getServerPort() ?: continue
}
testingJobs.add(GlobalScope.launch(Dispatchers.IO) {
configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing
it.testResult = Utils.tcping(serverAddress, serverPort)
val myJob = coroutineContext[Job]
launch(Dispatchers.Main) {
testingJobs.remove(myJob)
adapter.updateSelectedItem(k)
}
}
})
}
true true
} }
@@ -303,7 +264,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.request(Manifest.permission.CAMERA) .request(Manifest.permission.CAMERA)
.subscribe { .subscribe {
if (it) if (it)
startActivityForResult<ScannerActivity>(requestCode) startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
else else
toast(R.string.toast_permission_denied) toast(R.string.toast_permission_denied)
} }
@@ -389,9 +350,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
toast(R.string.toast_invalid_url) toast(R.string.toast_invalid_url)
return false return false
} }
doAsync { GlobalScope.launch(Dispatchers.IO) {
val configText = URL(url).readText() val configText = try {
uiThread { URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
importCustomizeConfig(configText) importCustomizeConfig(configText)
} }
} }
@@ -423,9 +389,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
continue continue
} }
Log.d("Main", url) Log.d("Main", url)
doAsync { GlobalScope.launch(Dispatchers.IO) {
val configText = URL(url).readText() val configText = try {
uiThread { URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
importBatchConfig(Utils.decode(configText), id) importBatchConfig(Utils.decode(configText), id)
} }
} }
@@ -504,35 +475,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// } // }
// } // }
private
var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(activity: MainActivity) : BroadcastReceiver() {
internal var mReference: SoftReference<MainActivity> = SoftReference(activity)
override fun onReceive(ctx: Context?, intent: Intent?) {
val activity = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
activity?.isRunning = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
activity?.isRunning = false
}
AppConfig.MSG_STATE_START_SUCCESS -> {
activity?.toast(R.string.toast_services_success)
activity?.isRunning = true
}
AppConfig.MSG_STATE_START_FAILURE -> {
activity?.toast(R.string.toast_services_failure)
activity?.isRunning = false
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
activity?.isRunning = false
}
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
moveTaskToBack(false) moveTaskToBack(false)
@@ -571,10 +513,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
when (item.itemId) { when (item.itemId) {
//R.id.server_profile -> activityClass = MainActivity::class.java //R.id.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> { R.id.sub_setting -> {
startActivity<SubSettingActivity>() startActivity(Intent(this, SubSettingActivity::class.java))
} }
R.id.settings -> { R.id.settings -> {
startActivity<SettingsActivity>("isRunning" to isRunning) startActivity(Intent(this, SettingsActivity::class.java)
.putExtra("isRunning", mainViewModel.isRunning.value == true))
} }
R.id.feedback -> { R.id.feedback -> {
Utils.openUri(this, AppConfig.v2rayNGIssues) Utils.openUri(this, AppConfig.v2rayNGIssues)
@@ -586,7 +529,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// startActivity<InappBuyActivity>() // startActivity<InappBuyActivity>()
} }
R.id.logcat -> { R.id.logcat -> {
startActivity<LogcatActivity>() startActivity(Intent(this, LogcatActivity::class.java))
} }
} }
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)

View File

@@ -1,15 +1,18 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.support.v7.app.AlertDialog
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.TextUtils import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
@@ -17,7 +20,6 @@ import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.android.synthetic.main.item_qrcode.view.* import kotlinx.android.synthetic.main.item_qrcode.view.*
import kotlinx.android.synthetic.main.item_recycler_main.view.* import kotlinx.android.synthetic.main.item_recycler_main.view.*
import org.jetbrains.anko.*
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -60,7 +62,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.name.text = remarks holder.name.text = remarks
holder.radio.isChecked = (position == configs.index) holder.radio.isChecked = (position == configs.index)
holder.itemView.backgroundColor = Color.TRANSPARENT holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = test_result holder.test_result.text = test_result
if (TextUtils.isEmpty(subid)) { if (TextUtils.isEmpty(subid)) {
@@ -85,7 +87,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
holder.layout_share.setOnClickListener { holder.layout_share.setOnClickListener {
mActivity.selector(null, shareOptions) { dialogInterface, i -> AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
try { try {
when (i) { when (i) {
0 -> { 0 -> {
@@ -94,14 +96,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} else { } else {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null) val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position)) iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
AlertDialog.Builder(mActivity).setView(iv).show()
mActivity.alert {
customView {
linearLayout {
addView(iv)
}
}
}.show()
} }
} }
1 -> { 1 -> {
@@ -117,18 +112,20 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }.show()
} }
holder.layout_edit.setOnClickListener { holder.layout_edit.setOnClickListener {
val intent = Intent().putExtra("position", position)
.putExtra("isRunning", !changeable)
if (configType == EConfigType.VMESS) { if (configType == EConfigType.VMESS) {
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable) mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
} else if (configType == EConfigType.CUSTOM) { } else if (configType == EConfigType.CUSTOM) {
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable) mActivity.startActivity(intent.setClass(mActivity, Server2Activity::class.java))
} else if (configType == EConfigType.SHADOWSOCKS) { } else if (configType == EConfigType.SHADOWSOCKS) {
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable) mActivity.startActivity(intent.setClass(mActivity, Server3Activity::class.java))
} else if (configType == EConfigType.SOCKS) { } else if (configType == EConfigType.SOCKS) {
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable) mActivity.startActivity(intent.setClass(mActivity, Server4Activity::class.java))
} }
} }
holder.layout_remove.setOnClickListener { holder.layout_remove.setOnClickListener {
@@ -146,16 +143,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} else { } else {
mActivity.showCircle() mActivity.showCircle()
Utils.stopVService(mActivity) Utils.stopVService(mActivity)
AngConfigManager.setActiveServer(position)
Observable.timer(500, TimeUnit.MILLISECONDS) Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
mActivity.showCircle() mActivity.showCircle()
if (!Utils.startVService(mActivity)) { if (!Utils.startVService(mActivity, position)) {
mActivity.hideCircle() mActivity.hideCircle()
} }
} }
} }
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -183,10 +178,10 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) { when (viewType) {
VIEW_TYPE_ITEM -> VIEW_TYPE_ITEM ->
return MainViewHolder(parent.context.layoutInflater return MainViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_main, parent, false)) .inflate(R.layout.item_recycler_main, parent, false))
else -> else ->
return FooterViewHolder(parent.context.layoutInflater return FooterViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_footer, parent, false)) .inflate(R.layout.item_recycler_footer, parent, false))
} }
} }

View File

@@ -4,6 +4,8 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
@@ -12,7 +14,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.animation.AccelerateInterpolator import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator import android.view.animation.DecelerateInterpolator
import com.dinuscxj.itemdecoration.LinearDividerItemDecoration
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.AppManagerUtil import com.v2ray.ang.util.AppManagerUtil
@@ -25,11 +26,12 @@ import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AppInfo import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import org.jetbrains.anko.doAsync import kotlinx.coroutines.Dispatchers
import org.jetbrains.anko.toast import kotlinx.coroutines.GlobalScope
import org.jetbrains.anko.uiThread import kotlinx.coroutines.launch
import java.net.URL import java.net.URL
class PerAppProxyActivity : BaseActivity() { class PerAppProxyActivity : BaseActivity() {
@@ -47,8 +49,7 @@ class PerAppProxyActivity : BaseActivity() {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
val dividerItemDecoration = LinearDividerItemDecoration( val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
this, LinearDividerItemDecoration.LINEAR_DIVIDER_VERTICAL)
recycler_view.addItemDecoration(dividerItemDecoration) recycler_view.addItemDecoration(dividerItemDecoration)
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null) val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
@@ -220,9 +221,14 @@ class PerAppProxyActivity : BaseActivity() {
private fun selectProxyApp() { private fun selectProxyApp() {
toast(R.string.msg_downloading_content) toast(R.string.msg_downloading_content)
val url = AppConfig.androidpackagenamelistUrl val url = AppConfig.androidpackagenamelistUrl
doAsync { GlobalScope.launch(Dispatchers.IO) {
val content = URL(url).readText() val content = try {
uiThread { URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
Log.d("selectProxyApp", content) Log.d("selectProxyApp", content)
selectProxyApp(content) selectProxyApp(content)
toast(R.string.toast_success) toast(R.string.toast_success)
@@ -276,4 +282,4 @@ class PerAppProxyActivity : BaseActivity() {
} }
return true return true
} }
} }

View File

@@ -2,14 +2,12 @@ package com.v2ray.ang.ui
import android.graphics.Color import android.graphics.Color
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AppInfo import com.v2ray.ang.dto.AppInfo
import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.* import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.*
import org.jetbrains.anko.image
import org.jetbrains.anko.layoutInflater
import org.jetbrains.anko.textColor
import java.util.* import java.util.*
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) : class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
@@ -45,7 +43,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater // VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
// .inflate(R.layout.item_recycler_bypass_list, parent, false)) // .inflate(R.layout.item_recycler_bypass_list, parent, false))
else -> AppViewHolder(ctx.layoutInflater else -> AppViewHolder(LayoutInflater.from(ctx)
.inflate(R.layout.item_recycler_bypass_list, parent, false)) .inflate(R.layout.item_recycler_bypass_list, parent, false))
} }
@@ -68,17 +66,17 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
fun bind(appInfo: AppInfo) { fun bind(appInfo: AppInfo) {
this.appInfo = appInfo this.appInfo = appInfo
icon.image = appInfo.appIcon icon.setImageDrawable(appInfo.appIcon)
// name.text = appInfo.appName // name.text = appInfo.appName
checkBox.isChecked = inBlacklist checkBox.isChecked = inBlacklist
package_name.text = appInfo.packageName package_name.text = appInfo.packageName
if (appInfo.isSystemApp) { if (appInfo.isSystemApp) {
name.text = String.format("** %1s", appInfo.appName) name.text = String.format("** %1s", appInfo.appName)
name.textColor = Color.RED name.setTextColor(Color.RED)
} else { } else {
name.text = appInfo.appName name.text = appInfo.appName
name.textColor = Color.DKGRAY name.setTextColor(Color.DKGRAY)
} }
itemView.setOnClickListener(this) itemView.setOnClickListener(this)
@@ -94,4 +92,4 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
} }
} }
} }
} }

View File

@@ -1,30 +1,24 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v4.app.Fragment import android.support.v4.app.Fragment
import android.text.TextUtils
import android.util.Log
import android.view.* import android.view.*
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.fragment_routing_settings.* import kotlinx.android.synthetic.main.fragment_routing_settings.*
import org.jetbrains.anko.toast
import android.view.MenuInflater import android.view.MenuInflater
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import org.jetbrains.anko.doAsync import com.v2ray.ang.extension.toast
import org.jetbrains.anko.startActivityForResult import kotlinx.coroutines.Dispatchers
import org.jetbrains.anko.support.v4.startActivityForResult import kotlinx.coroutines.GlobalScope
import org.jetbrains.anko.support.v4.toast import kotlinx.coroutines.launch
import org.jetbrains.anko.uiThread
import java.net.URL import java.net.URL
class RoutingSettingsFragment : Fragment() { class RoutingSettingsFragment : Fragment() {
companion object { companion object {
private const val routing_arg = "routing_arg" private const val routing_arg = "routing_arg"
@@ -96,7 +90,7 @@ class RoutingSettingsFragment : Fragment() {
.request(Manifest.permission.CAMERA) .request(Manifest.permission.CAMERA)
.subscribe { .subscribe {
if (it) if (it)
startActivityForResult<ScannerActivity>(requestCode) startActivityForResult(Intent(activity, ScannerActivity::class.java), requestCode)
else else
activity?.toast(R.string.toast_permission_denied) activity?.toast(R.string.toast_permission_denied)
} }
@@ -118,12 +112,17 @@ class RoutingSettingsFragment : Fragment() {
} }
} }
toast(R.string.msg_downloading_content) activity?.toast(R.string.msg_downloading_content)
doAsync { GlobalScope.launch(Dispatchers.IO) {
val content = URL(url).readText() val content = try {
uiThread { URL(url).readText()
et_routing_content.text = Utils.getEditable(content!!) } catch (e: Exception) {
toast(R.string.toast_success) e.printStackTrace()
""
}
launch(Dispatchers.Main) {
et_routing_content.text = Utils.getEditable(content)
activity?.toast(R.string.toast_success)
} }
} }
return true return true

View File

@@ -6,7 +6,7 @@ import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import android.os.Bundle import android.os.Bundle
import org.jetbrains.anko.* import com.v2ray.ang.extension.toast
class ScScannerActivity : BaseActivity() { class ScScannerActivity : BaseActivity() {
companion object { companion object {
@@ -24,7 +24,7 @@ class ScScannerActivity : BaseActivity() {
.request(Manifest.permission.CAMERA) .request(Manifest.permission.CAMERA)
.subscribe { .subscribe {
if (it) if (it)
startActivityForResult<ScannerActivity>(requestCode) startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
else else
toast(R.string.toast_permission_denied) toast(R.string.toast_permission_denied)
} }
@@ -43,10 +43,10 @@ class ScScannerActivity : BaseActivity() {
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
} }
startActivity<MainActivity>() startActivity(Intent(this, MainActivity::class.java))
} }
} }
finish() finish()
} }
} }

View File

@@ -7,23 +7,13 @@ import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView import me.dm7.barcodescanner.zxing.ZXingScannerView
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.icu.util.TimeUnit
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.zxing.BarcodeFormat import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.QRCodeDecoder import com.v2ray.ang.util.QRCodeDecoder
import org.jetbrains.anko.toast
import rx.Observable
import android.os.SystemClock
import kotlinx.android.synthetic.main.activity_main.*
import rx.Observer
import rx.android.schedulers.AndroidSchedulers
import javax.xml.datatype.DatatypeConstants.SECONDS
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler { class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
companion object { companion object {

View File

@@ -1,8 +1,10 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.Editable import android.text.Editable
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.google.gson.Gson import com.google.gson.Gson
@@ -10,13 +12,12 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server2.* import kotlinx.android.synthetic.main.activity_server2.*
import org.jetbrains.anko.*
import java.lang.Exception import java.lang.Exception
class Server2Activity : BaseActivity() { class Server2Activity : BaseActivity() {
companion object { companion object {
private const val REQUEST_SCAN = 1 private const val REQUEST_SCAN = 1
@@ -100,6 +101,11 @@ class Server2Activity : BaseActivity() {
if (saveSuccess) { if (saveSuccess) {
//update config //update config
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString()) defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
if (edit_index == configs.index) {
if (!AngConfigManager.genStoreV2rayConfig(edit_index)) {
Log.d(AppConfig.ANG_PACKAGE, "update custom config $edit_index but generate full configuration failed!")
}
}
finish() finish()
return true return true
} else { } else {
@@ -112,17 +118,16 @@ class Server2Activity : BaseActivity() {
*/ */
fun deleteServer(): Boolean { fun deleteServer(): Boolean {
if (edit_index >= 0) { if (edit_index >= 0) {
alert(R.string.del_config_comfirm) { AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
positiveButton(android.R.string.ok) { .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) { if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
}
} }
} .show()
show()
}
} else { } else {
} }
return true return true
@@ -158,4 +163,4 @@ class Server2Activity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -1,16 +1,16 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server3.* import kotlinx.android.synthetic.main.activity_server3.*
import org.jetbrains.anko.*
class Server3Activity : BaseActivity() { class Server3Activity : BaseActivity() {
companion object { companion object {
@@ -126,17 +126,16 @@ class Server3Activity : BaseActivity() {
*/ */
fun deleteServer(): Boolean { fun deleteServer(): Boolean {
if (edit_index >= 0) { if (edit_index >= 0) {
alert(R.string.del_config_comfirm) { AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
positiveButton(android.R.string.ok) { .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) { if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
}
} }
} .show()
show()
}
} else { } else {
} }
return true return true
@@ -172,4 +171,4 @@ class Server3Activity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -1,16 +1,16 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server4.* import kotlinx.android.synthetic.main.activity_server4.*
import org.jetbrains.anko.*
class Server4Activity : BaseActivity() { class Server4Activity : BaseActivity() {
companion object { companion object {
@@ -110,17 +110,16 @@ class Server4Activity : BaseActivity() {
*/ */
fun deleteServer(): Boolean { fun deleteServer(): Boolean {
if (edit_index >= 0) { if (edit_index >= 0) {
alert(R.string.del_config_comfirm) { AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
positiveButton(android.R.string.ok) { .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) { if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
}
} }
} .show()
show()
}
} else { } else {
} }
return true return true
@@ -156,4 +155,4 @@ class Server4Activity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -1,16 +1,16 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server.* import kotlinx.android.synthetic.main.activity_server.*
import org.jetbrains.anko.*
class ServerActivity : BaseActivity() { class ServerActivity : BaseActivity() {
companion object { companion object {
@@ -169,17 +169,16 @@ class ServerActivity : BaseActivity() {
*/ */
fun deleteServer(): Boolean { fun deleteServer(): Boolean {
if (edit_index >= 0) { if (edit_index >= 0) {
alert(R.string.del_config_comfirm) { AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
positiveButton(android.R.string.ok) { .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) { if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
}
} }
} .show()
show()
}
} else { } else {
} }
return true return true
@@ -215,4 +214,4 @@ class ServerActivity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -1,23 +1,16 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.preference.* import android.support.v7.preference.*
import com.v2ray.ang.AngApplication import android.view.View
import com.v2ray.ang.BuildConfig
//import com.v2ray.ang.InappBuyActivity
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.onClick import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import org.jetbrains.anko.act import com.v2ray.ang.viewmodel.SettingsViewModel
import org.jetbrains.anko.defaultSharedPreferences
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.toast
import libv2ray.Libv2ray
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
companion object { companion object {
@@ -42,11 +35,12 @@ class SettingsActivity : BaseActivity() {
// const val PREF_LICENSES = "pref_licenses" // const val PREF_LICENSES = "pref_licenses"
// const val PREF_FEEDBACK = "pref_feedback" // const val PREF_FEEDBACK = "pref_feedback"
// const val PREF_TG_GROUP = "pref_tg_group" // const val PREF_TG_GROUP = "pref_tg_group"
const val PREF_VERSION = "pref_version"
// const val PREF_AUTO_RESTART = "pref_auto_restart" // const val PREF_AUTO_RESTART = "pref_auto_restart"
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6" const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
} }
private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) setContentView(R.layout.activity_settings)
@@ -54,20 +48,22 @@ class SettingsActivity : BaseActivity() {
title = getString(R.string.title_settings) title = getString(R.string.title_settings)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
settingsViewModel.startListenPreferenceChange()
} }
class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener { class SettingsFragment : PreferenceFragmentCompat() {
val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference } private val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference } private val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference }
val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference } private val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference }
val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference } private val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference }
val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference } private val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference } private val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference }
val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference } private val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference } private val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference } private val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference } private val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference } // val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
@@ -75,32 +71,31 @@ class SettingsActivity : BaseActivity() {
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference } // val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference } // val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) } private val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) }
// val donate: Preference by lazy { findPreference(PREF_DONATE) } // val donate: Preference by lazy { findPreference(PREF_DONATE) }
// val licenses: Preference by lazy { findPreference(PREF_LICENSES) } // val licenses: Preference by lazy { findPreference(PREF_LICENSES) }
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) } // val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) } // val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
val version: Preference by lazy { findPreference(PREF_VERSION) }
private val mode by lazy { findPreference(AppConfig.PREF_MODE) as ListPreference }
private fun restartProxy() { private fun restartProxy() {
Utils.stopVService(activity) Utils.stopVService(requireContext())
Utils.startVService(activity) Utils.startVService(requireContext(), AngConfigManager.configs.index)
} }
private fun isRunning(): Boolean { private fun isRunning(): Boolean {
return false //TODO no point of adding logic now since Settings will be changed soon return false //TODO no point of adding logic now since Settings will be changed soon
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreatePreferences(bundle: Bundle?, s: String?) {
super.onCreate(savedInstanceState)
addPreferencesFromResource(R.xml.pref_settings) addPreferencesFromResource(R.xml.pref_settings)
var app = activity.application as AngApplication
perAppProxy.setOnPreferenceClickListener { perAppProxy.setOnPreferenceClickListener {
if (isRunning()) { if (isRunning()) {
Utils.stopVService(activity) Utils.stopVService(requireContext())
} }
startActivity<PerAppProxyActivity>() startActivity(Intent(activity, PerAppProxyActivity::class.java))
perAppProxy.isChecked = true perAppProxy.isChecked = true
true true
} }
@@ -117,7 +112,7 @@ class SettingsActivity : BaseActivity() {
proxySharing.setOnPreferenceClickListener { proxySharing.setOnPreferenceClickListener {
if (proxySharing.isChecked) if (proxySharing.isChecked)
toast(R.string.toast_warning_pref_proxysharing) activity?.toast(R.string.toast_warning_pref_proxysharing)
if (isRunning()) if (isRunning())
restartProxy() restartProxy()
true true
@@ -134,10 +129,11 @@ class SettingsActivity : BaseActivity() {
true true
} }
routingCustom.onClick { routingCustom.setOnPreferenceClickListener {
if (isRunning()) if (isRunning())
Utils.stopVService(activity) Utils.stopVService(requireContext())
startActivity<RoutingSettingsActivity>() startActivity(Intent(activity, RoutingSettingsActivity::class.java))
false
} }
forwardIpv6.setOnPreferenceClickListener { forwardIpv6.setOnPreferenceClickListener {
@@ -153,7 +149,7 @@ class SettingsActivity : BaseActivity() {
} }
domesticDns.setOnPreferenceChangeListener { preference, any -> domesticDns.setOnPreferenceChangeListener { _, any ->
// domesticDns.summary = any as String // domesticDns.summary = any as String
val nval = any as String val nval = any as String
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
@@ -162,7 +158,7 @@ class SettingsActivity : BaseActivity() {
true true
} }
remoteDns.setOnPreferenceChangeListener { preference, any -> remoteDns.setOnPreferenceChangeListener { _, any ->
// remoteDns.summary = any as String // remoteDns.summary = any as String
val nval = any as String val nval = any as String
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
@@ -171,6 +167,12 @@ class SettingsActivity : BaseActivity() {
true true
} }
mode.setOnPreferenceChangeListener { _, newValue ->
updatePerAppProxy(newValue.toString())
true
}
mode.dialogLayoutResource = R.layout.preference_with_help_link
// donate.onClick { // donate.onClick {
// startActivity<InappBuyActivity>() // startActivity<InappBuyActivity>()
// } // }
@@ -206,14 +208,12 @@ class SettingsActivity : BaseActivity() {
// httpPort.summary = any as String // httpPort.summary = any as String
// true // true
// } // }
version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false) updatePerAppProxy(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "") remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "")
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "") domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "")
@@ -227,24 +227,21 @@ class SettingsActivity : BaseActivity() {
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808") // socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "") // lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this)
} }
override fun onStop() { private fun updatePerAppProxy(mode: String?) {
super.onStop() if (mode == "VPN") {
defaultSharedPreferences.unregisterOnSharedPreferenceChangeListener(this) perAppProxy.isEnabled = true
} perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(PREF_PER_APP_PROXY, false)
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { } else {
when (key) { perAppProxy.isEnabled = false
// PREF_AUTO_RESTART -> perAppProxy.isChecked = false
// act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
PREF_PER_APP_PROXY ->
act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
} }
} }
} }
fun onModeHelpClicked(view: View) {
Utils.openUri(this, AppConfig.v2rayNGWikiMode)
}
} }

View File

@@ -1,16 +1,16 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.os.Bundle import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_sub_edit.* import kotlinx.android.synthetic.main.activity_sub_edit.*
import org.jetbrains.anko.*
class SubEditActivity : BaseActivity() { class SubEditActivity : BaseActivity() {
@@ -95,17 +95,16 @@ class SubEditActivity : BaseActivity() {
*/ */
fun deleteServer(): Boolean { fun deleteServer(): Boolean {
if (edit_index >= 0) { if (edit_index >= 0) {
alert(R.string.del_config_comfirm) { AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
positiveButton(android.R.string.ok) { .setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeSubItem(edit_index) == 0) { if (AngConfigManager.removeSubItem(edit_index) == 0) {
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
} else { } else {
toast(R.string.toast_failure) toast(R.string.toast_failure)
}
} }
} .show()
show()
}
} else { } else {
} }
return true return true
@@ -135,4 +134,4 @@ class SubEditActivity : BaseActivity() {
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }

View File

@@ -1,12 +1,12 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.content.Intent
import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.LinearLayoutManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import com.v2ray.ang.R import com.v2ray.ang.R
import kotlinx.android.synthetic.main.activity_sub_setting.* import kotlinx.android.synthetic.main.activity_sub_setting.*
import android.os.Bundle import android.os.Bundle
import org.jetbrains.anko.startActivity
class SubSettingActivity : BaseActivity() { class SubSettingActivity : BaseActivity() {
@@ -40,7 +40,9 @@ class SubSettingActivity : BaseActivity() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.add_config -> { R.id.add_config -> {
startActivity<SubEditActivity>("position" to -1) startActivity(Intent(this, SubEditActivity::class.java)
.putExtra("position", -1)
)
adapter.updateConfigList() adapter.updateConfigList()
true true
} }
@@ -48,4 +50,4 @@ class SubSettingActivity : BaseActivity() {
} }
} }

View File

@@ -1,14 +1,15 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.support.v7.widget.RecyclerView import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.* import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
import org.jetbrains.anko.*
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() { class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
@@ -28,17 +29,19 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
holder.name.text = remarks holder.name.text = remarks
holder.url.text = url holder.url.text = url
holder.itemView.backgroundColor = Color.TRANSPARENT holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.layout_edit.setOnClickListener { holder.layout_edit.setOnClickListener {
mActivity.startActivity<SubEditActivity>("position" to position) mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
.putExtra("position", position)
)
} }
} else { } else {
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return MainViewHolder(parent.context.layoutInflater return MainViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_sub_setting, parent, false)) .inflate(R.layout.item_recycler_sub_setting, parent, false))
} }

View File

@@ -2,6 +2,7 @@ package com.v2ray.ang.util
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
@@ -154,6 +155,10 @@ object AngConfigManager {
angConfig.index = index angConfig.index = index
app.curIndex = index app.curIndex = index
storeConfigFile() storeConfigFile()
if (!genStoreV2rayConfig(index)) {
Log.d(AppConfig.ANG_PACKAGE, "set active index $index but generate full configuration failed!")
return -1
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
app.curIndex = -1 app.curIndex = -1
@@ -318,7 +323,7 @@ object AngConfigManager {
result = Utils.decode(result) result = Utils.decode(result)
} }
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".toRegex() val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result) val match = legacyPattern.matchEntire(result)
if (match == null) { if (match == null) {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
@@ -586,9 +591,9 @@ object AngConfigManager {
*/ */
fun shareFullContent2Clipboard(index: Int): Int { fun shareFullContent2Clipboard(index: Int): Int {
try { try {
if (AngConfigManager.genStoreV2rayConfig(index)) { val result = V2rayConfigUtil.getV2rayConfig(app, angConfig.vmess[index])
val configContent = app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") if (result.status) {
Utils.setClipboard(app.applicationContext, configContent) Utils.setClipboard(app.applicationContext, result.content)
} else { } else {
return -1 return -1
} }

View File

@@ -22,20 +22,18 @@ import android.webkit.URLUtil
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.responseLength import com.v2ray.ang.extension.responseLength
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.service.V2RayServiceManager import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import me.dozen.dpreference.DPreference import me.dozen.dpreference.DPreference
import org.jetbrains.anko.toast
import java.io.IOException import java.io.IOException
import java.net.* import java.net.*
import libv2ray.Libv2ray
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
object Utils { object Utils {
val tcpTestingSockets = ArrayList<Socket?>() val tcpTestingSockets = ArrayList<Socket?>()
@@ -273,38 +271,13 @@ object Utils {
} }
fun startVServiceFromToggle(context: Context): Boolean { fun startVServiceFromToggle(context: Context): Boolean {
val result = startVService(context) val result = context.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
if (!result) { if (result.isBlank()) {
context.toast(R.string.app_tile_first_use) context.toast(R.string.app_tile_first_use)
}
return result
}
/**
* startVService
*/
fun startVService(context: Context): Boolean {
if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{
context.toast(R.string.toast_services_start)
}
if (AngConfigManager.genStoreV2rayConfig(-1)) {
val configContent = AngConfigManager.currGeneratedV2rayConfig()
val configType = AngConfigManager.currConfigType()
if (configType == EConfigType.CUSTOM) {
try {
Libv2ray.testConfig(configContent)
} catch (e: Exception) {
context.toast(e.toString())
return false
}
}
V2RayServiceManager.startV2Ray(context, context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN"))
return true
} else {
return false return false
} }
V2RayServiceManager.startV2Ray(context)
return true
} }
/** /**
@@ -320,8 +293,11 @@ object Utils {
* startVService * startVService
*/ */
fun startVService(context: Context, index: Int): Boolean { fun startVService(context: Context, index: Int): Boolean {
AngConfigManager.setActiveServer(index) if (AngConfigManager.setActiveServer(index) < 0) {
return startVService(context) return false
}
V2RayServiceManager.startV2Ray(context)
return true
} }
/** /**

View File

@@ -3,9 +3,7 @@ package com.v2ray.ang.util
import android.content.Context import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.google.gson.Gson import com.google.gson.*
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AngConfig.VmessBean import com.v2ray.ang.dto.AngConfig.VmessBean
@@ -14,7 +12,6 @@ import com.v2ray.ang.ui.SettingsActivity
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONArray import org.json.JSONArray
import com.google.gson.JsonObject
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -64,11 +61,16 @@ object V2rayConfigUtil {
if (TextUtils.isEmpty(jsonConfig)) { if (TextUtils.isEmpty(jsonConfig)) {
return null return null
} }
val v2rayConfig = Gson().fromJson(jsonConfig, V2rayConfig::class.java) ?: return null val v2rayConfig: V2rayConfig? = try {
for (outbound in v2rayConfig.outbounds) { Gson().fromJson(jsonConfig, V2rayConfig::class.java)
if (outbound.protocol.equals(EConfigType.VMESS.name.toLowerCase()) || } catch (e: JsonSyntaxException) {
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || e.printStackTrace()
outbound.protocol.equals(EConfigType.SOCKS.name.toLowerCase())) { null
}
v2rayConfig?.outbounds?.forEach { outbound ->
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
outbound.protocol.equals(EConfigType.SOCKS.name, true)) {
return outbound return outbound
} }
} }

View File

@@ -0,0 +1,101 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.MutableLiveData
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.coroutines.*
class MainViewModel(application: Application) : AndroidViewModel(application) {
val isRunning by lazy { MutableLiveData<Boolean>() }
val updateListAction by lazy { MutableLiveData<Int>() }
val updateTestResultAction by lazy { MutableLiveData<String>() }
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
fun startListenBroadcast() {
isRunning.value = false
getApplication<AngApplication>().registerReceiver(mMsgReceiver, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(getApplication(), AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onCleared() {
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared")
super.onCleared()
}
fun testAllTcping() {
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
Utils.closeAllTcpSockets()
for (k in 0 until AngConfigManager.configs.vmess.count()) {
AngConfigManager.configs.vmess[k].testResult = ""
updateListAction.value = -1 // update all
}
for (k in 0 until AngConfigManager.configs.vmess.count()) {
var serverAddress = AngConfigManager.configs.vmess[k].address
var serverPort = AngConfigManager.configs.vmess[k].port
if (AngConfigManager.configs.vmess[k].configType == EConfigType.CUSTOM.value) {
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(getApplication(),
AngConfigManager.configs.vmess[k].guid) ?: continue
serverAddress = serverOutbound.getServerAddress() ?: continue
serverPort = serverOutbound.getServerPort() ?: continue
}
tcpingTestScope.launch {
AngConfigManager.configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing
it.testResult = Utils.tcping(serverAddress, serverPort)
launch(Dispatchers.Main) {
updateListAction.value = k
}
}
}
}
}
fun testCurrentServerRealPing() {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
GlobalScope.launch(Dispatchers.IO) {
val result = Utils.testConnection(getApplication(), socksPort)
launch(Dispatchers.Main) {
updateTestResultAction.value = result
}
}
}
private val mMsgReceiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
isRunning.value = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
isRunning.value = false
}
AppConfig.MSG_STATE_START_SUCCESS -> {
getApplication<AngApplication>().toast(R.string.toast_services_success)
isRunning.value = true
}
AppConfig.MSG_STATE_START_FAILURE -> {
getApplication<AngApplication>().toast(R.string.toast_services_failure)
isRunning.value = false
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
isRunning.value = false
}
}
}
}
}

View File

@@ -0,0 +1,46 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.content.SharedPreferences
import android.support.v7.preference.PreferenceManager
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.ui.SettingsActivity.Companion
import com.v2ray.ang.util.AngConfigManager
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class SettingsViewModel(application: Application) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
fun startListenPreferenceChange() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).registerOnSharedPreferenceChangeListener(this)
}
override fun onCleared() {
PreferenceManager.getDefaultSharedPreferences(getApplication()).unregisterOnSharedPreferenceChangeListener(this)
Log.i(AppConfig.ANG_PACKAGE, "Settings ViewModel is cleared")
super.onCleared()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
Log.d(AppConfig.ANG_PACKAGE, "Observe settings changed: $key")
when(key) {
Companion.PREF_SNIFFING_ENABLED,
Companion.PREF_PROXY_SHARING,
Companion.PREF_LOCAL_DNS_ENABLED,
Companion.PREF_REMOTE_DNS,
Companion.PREF_DOMESTIC_DNS,
Companion.PREF_ROUTING_DOMAIN_STRATEGY,
Companion.PREF_ROUTING_MODE,
AppConfig.PREF_V2RAY_ROUTING_AGENT,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT -> {
GlobalScope.launch {
if (!AngConfigManager.genStoreV2rayConfig(AngConfigManager.configs.index)) {
Log.d(AppConfig.ANG_PACKAGE, "$key changed but generate full configuration failed!")
}
}
}
}
}
}

View File

@@ -111,7 +111,23 @@
app:headerLayout="@layout/nav_header" app:headerLayout="@layout/nav_header"
app:itemIconTint="@color/colorPrimary_dark" app:itemIconTint="@color/colorPrimary_dark"
app:itemTextColor="@color/colorPrimary" app:itemTextColor="@color/colorPrimary"
app:menu="@menu/menu_drawer" /> app:menu="@menu/menu_drawer" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/white"
android:padding="14dp">
<TextView
android:id="@+id/version"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:textColor="@color/accent" />
</LinearLayout>
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout> </android.support.v4.widget.DrawerLayout>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:onClick="onModeHelpClicked"
android:text="@string/title_mode_help"
android:textAlignment="textStart"
android:textStyle="italic" />

View File

@@ -16,25 +16,27 @@
</group> </group>
<item android:title="@string/title_about"> <group android:id="@+id/group_id2">
<menu> <item
<item
android:id="@+id/promotion" android:id="@+id/promotion"
android:icon="@drawable/ic_whatshot_white_24dp" android:icon="@drawable/ic_whatshot_white_24dp"
android:title="@string/title_pref_promotion" /> android:title="@string/title_pref_promotion" />
<item <item
android:id="@+id/donate" android:id="@+id/donate"
android:icon="@drawable/ic_attach_money_white_24dp" android:icon="@drawable/ic_attach_money_white_24dp"
android:title="@string/title_pref_donate" /> android:title="@string/title_pref_donate" />
<item <item
android:id="@+id/logcat" android:id="@+id/logcat"
android:icon="@drawable/ic_logcat_white_24dp" android:icon="@drawable/ic_logcat_white_24dp"
android:title="@string/title_logcat" /> android:title="@string/title_logcat" />
<item <item
android:id="@+id/feedback" android:id="@+id/feedback"
android:icon="@drawable/ic_feedback_white_24dp" android:icon="@drawable/ic_feedback_white_24dp"
android:title="@string/title_pref_feedback" /> android:title="@string/title_pref_feedback" />
</menu> <!-- place holder for version text at the bottom -->
</item> <item
android:id="@+id/placeholder"
android:enabled="false"
android:title="" />
</group>
</menu> </menu>

View File

@@ -17,17 +17,6 @@
<copyright>Copyright(C) 2008-2011 The Android Open Source Project</copyright> <copyright>Copyright(C) 2008-2011 The Android Open Source Project</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice>
<name>Apache Commons Validator</name>
<url>http://commons.apache.org/proper/commons-collections/</url>
<copyright>Copyright(C) 2001-2014 The Apache Software Foundation</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>anko</name>
<url>https://github.com/Kotlin/anko</url>
<license>Apache Software License 2.0</license>
</notice>
<notice> <notice>
<name>Google Gson</name> <name>Google Gson</name>
<url>https://github.com/google/gson</url> <url>https://github.com/google/gson</url>
@@ -52,33 +41,9 @@
<copyright>Copyright 2015 Square, Inc.</copyright> <copyright>Copyright 2015 Square, Inc.</copyright>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice>
<name>ReactiveNetwork</name>
<url>https://github.com/pwittchen/ReactiveNetwork</url>
<copyright>Copyright 2016 Piotr Wittchen</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>RecyclerItemDecoration</name>
<url>https://github.com/dinuscxj/RecyclerItemDecoration</url>
<copyright>Copyright 2015-2019 dinus</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice>
<name>RxKotlin</name>
<url>https://github.com/ReactiveX/RxKotlin</url>
<copyright>Copyright 2012 Netflix, Inc.</copyright>
<license>Apache Software License 2.0</license>
</notice>
<notice> <notice>
<name>RxPermissions</name> <name>RxPermissions</name>
<url>https://github.com/tbruyelle/RxPermissions</url> <url>https://github.com/tbruyelle/RxPermissions</url>
<license>Apache Software License 2.0</license> <license>Apache Software License 2.0</license>
</notice> </notice>
<notice> </notices>
<name>RecyclerItemDecoration</name>
<url>https://github.com/dinuscxj/RecyclerItemDecoration</url>
<copyright>Copyright 2015-2019 dinus</copyright>
<license>Apache Software License 2.0</license>
</notice>
</notices>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://stackoverflow.com/a/52960668/5495739 -->
<resources xmlns:tools="http://schemas.android.com/tools">
<bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,PrivateResource">false</bool>
<dimen name="preference_category_padding_start" tools:ignore="MissingDefaultResource,PrivateResource">0dp</dimen>
</resources>

View File

@@ -73,7 +73,6 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">设置</string> <string name="title_settings">设置</string>
<string name="title_about">关于</string>
<string name="title_advanced">进阶设置</string> <string name="title_advanced">进阶设置</string>
<string name="title_pref_per_app_proxy">分应用代理</string> <string name="title_pref_per_app_proxy">分应用代理</string>
@@ -124,7 +123,7 @@
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string> <string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
<string name="title_mode">模式</string> <string name="title_mode">模式</string>
<string name="title_pref_version">版本</string> <string name="title_mode_help">点此查看更多帮助</string>
<string name="donate_error_setup">初始化错误:</string> <string name="donate_error_setup">初始化错误:</string>
<string name="donate_error_inventory">无法查询到项目</string> <string name="donate_error_inventory">无法查询到项目</string>
@@ -188,4 +187,9 @@
<string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string> <string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string>
<string name="toast_malformed_josn">配置格式错误</string> <string name="toast_malformed_josn">配置格式错误</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>仅代理</item>
</string-array>
</resources> </resources>

View File

@@ -74,7 +74,6 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">設定</string> <string name="title_settings">設定</string>
<string name="title_about">關於</string>
<string name="title_advanced">進階設定</string> <string name="title_advanced">進階設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string> <string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
@@ -126,7 +125,7 @@
<string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string> <string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string>
<string name="title_mode">模式</string> <string name="title_mode">模式</string>
<string name="title_pref_version">版本</string> <string name="title_mode_help">點此查看更多幫助</string>
<string name="donate_error_setup">錯誤設定:</string> <string name="donate_error_setup">錯誤設定:</string>
<string name="donate_error_inventory">Error querying inventory</string> <string name="donate_error_inventory">Error querying inventory</string>
@@ -189,4 +188,10 @@
<string name="toast_warning_pref_proxysharing">其他設備可以使用socks/http協定通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網路中啟用以避免未經授權的連接</string> <string name="toast_warning_pref_proxysharing">其他設備可以使用socks/http協定通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網路中啟用以避免未經授權的連接</string>
<string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string> <string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string>
<string name="toast_malformed_josn">配置格式錯誤</string> <string name="toast_malformed_josn">配置格式錯誤</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>僅代理</item>
</string-array>
</resources> </resources>

View File

@@ -74,7 +74,6 @@
<!-- Preferences --> <!-- Preferences -->
<string name="title_settings">Settings</string> <string name="title_settings">Settings</string>
<string name="title_about">About</string>
<string name="title_advanced">Advanced Settings</string> <string name="title_advanced">Advanced Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string> <string name="title_pref_per_app_proxy">Per-app proxy</string>
@@ -126,7 +125,7 @@
<string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string> <string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string>
<string name="title_mode">Mode</string> <string name="title_mode">Mode</string>
<string name="title_pref_version">Version</string> <string name="title_mode_help">Click me for more help</string>
<string name="donate_error_setup">Error Setup:</string> <string name="donate_error_setup">Error Setup:</string>
<string name="donate_error_inventory">Error querying inventory</string> <string name="donate_error_inventory">Error querying inventory</string>
@@ -190,4 +189,9 @@
<string name="toast_warning_pref_proxysharing_short">Proxy sharing enabled\nMake sure you are in a trusted network</string> <string name="toast_warning_pref_proxysharing_short">Proxy sharing enabled\nMake sure you are in a trusted network</string>
<string name="toast_malformed_josn">Config malformed</string> <string name="toast_malformed_josn">Config malformed</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>Proxy only</item>
</string-array>
</resources> </resources>

View File

@@ -88,34 +88,10 @@
<ListPreference <ListPreference
android:defaultValue="VPN" android:defaultValue="VPN"
android:entries="@array/mode_value" android:entries="@array/mode_entries"
android:entryValues="@array/mode_value" android:entryValues="@array/mode_value"
android:key="pref_mode" android:key="pref_mode"
android:summary="%s" android:summary="%s"
android:title="@string/title_mode" /> android:title="@string/title_mode" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/title_about">
<!--<Preference-->
<!--android:key="pref_donate"-->
<!--android:summary="@string/summary_pref_donate"-->
<!--android:title="@string/title_pref_donate" />-->
<!--<Preference-->
<!--android:key="pref_licenses"-->
<!--android:title="@string/notices_title" />-->
<!--<Preference-->
<!--android:key="pref_feedback"-->
<!--android:summary="@string/summary_pref_feedback"-->
<!--android:title="@string/title_pref_feedback" />-->
<!--<Preference-->
<!--android:key="pref_tg_group"-->
<!--android:title="@string/summary_pref_tg_group" />-->
<Preference
android:key="pref_version"
android:title="@string/title_pref_version" />
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -7,7 +7,7 @@ buildscript {
maven { url 'https://maven.google.com' } maven { url 'https://maven.google.com' }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.1.3' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@@ -19,7 +19,7 @@ android {
dependencies { dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs') implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.13'
implementation "com.android.support:support-annotations:$supportLibVersion" implementation "com.android.support:support-annotations:$supportLibVersion"
implementation 'com.google.code.gson:gson:2.7' implementation 'com.google.code.gson:gson:2.8.6'
} }

View File

@@ -13,10 +13,9 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
#Fri Jun 02 14:08:42 CST 2017 #Fri Jun 02 14:08:42 CST 2017
ankoVersion=0.10.8 kotlinVersion=1.4.10
kotlinVersion=1.3.40
supportLibVersion=28.0.0 supportLibVersion=28.0.0
buildToolsVer=29.0.3 buildToolsVer=30.0.2
compileSdkVer=29 compileSdkVer=30
kotlin.incremental=true kotlin.incremental=true
targetSdkVer=29 targetSdkVer=30