Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c199c1687 | ||
|
|
7b2794f6be | ||
|
|
411d9e5c9a | ||
|
|
a57aee9424 | ||
|
|
4602afc67e | ||
|
|
ceb29840f2 | ||
|
|
1c1f130ca7 | ||
|
|
afa0eb9375 | ||
|
|
49b682f0f3 | ||
|
|
d5def3bf2f | ||
|
|
51b97b64f2 | ||
|
|
3af9ce1a1a | ||
|
|
a5d3dda941 | ||
|
|
71a3e300d8 | ||
|
|
030b9a3900 | ||
|
|
24d105a53c | ||
|
|
45805d6df7 | ||
|
|
4437e6699b | ||
|
|
fa409f91e4 | ||
|
|
89be5f077b | ||
|
|
896889778f | ||
|
|
10f705a8b2 | ||
|
|
2956fa2030 | ||
|
|
c2a704a6ea | ||
|
|
78cac0cd90 | ||
|
|
3ffb2e8e05 | ||
|
|
e2d667e0bb | ||
|
|
3ae0777d7f | ||
|
|
5e6348676c | ||
|
|
df3f1ca3ef | ||
|
|
94f2bec329 | ||
|
|
c54d8fa43a | ||
|
|
5bbbdcf6f2 | ||
|
|
4abf20fa32 | ||
|
|
723727feb9 | ||
|
|
2efd4b741c | ||
|
|
83aab0f880 | ||
|
|
66ea17877e | ||
|
|
26bc985368 | ||
|
|
5bbf40c784 | ||
|
|
6d5c23245c | ||
|
|
b148290211 | ||
|
|
c473f9bb13 | ||
|
|
c7c3d27f36 | ||
|
|
bebc6fea13 | ||
|
|
9271857b1e | ||
|
|
7ea78c1840 | ||
|
|
ac668788b3 | ||
|
|
adfcf0a5d9 | ||
|
|
15b5595797 | ||
|
|
5a18296cb2 | ||
|
|
1256edbaf5 |
@@ -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/geoip.dat > geoip.dat
|
||||
|
||||
shippedBinary:
|
||||
cd shippedBinarys; $(MAKE) shippedBinary
|
||||
|
||||
fetchDep:
|
||||
-go get github.com/2dust/AndroidLibV2rayLite
|
||||
go get github.com/2dust/AndroidLibV2rayLite
|
||||
@@ -30,5 +27,5 @@ downloadGoMobile:
|
||||
BuildMobile:
|
||||
@echo Stub
|
||||
|
||||
all: asset pb shippedBinary fetchDep
|
||||
all: asset pb fetchDep
|
||||
@echo DONE
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
||||
)
|
||||
|
||||
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, sendFd func() int) {
|
||||
func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string) {
|
||||
log.Println(proc, pt)
|
||||
count := 0
|
||||
for count <= 42 {
|
||||
@@ -37,13 +37,6 @@ func (v *Escorting) EscortRun(proc string, pt []string, additionalEnv string, se
|
||||
*v.escortProcess = append(*v.escortProcess, cmd.Process)
|
||||
log.Println("EscortRun Waiting....")
|
||||
|
||||
if count > 0 {
|
||||
go func() {
|
||||
time.Sleep(time.Second)
|
||||
sendFd()
|
||||
}()
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
log.Println("EscortRun cmd.Wait err:", err)
|
||||
}
|
||||
|
||||
@@ -34,14 +34,10 @@ $NDK_HOME/ndk-build \
|
||||
NDK_OUT=$TMPDIR/tmp \
|
||||
APP_SHORT_COMMANDS=false LOCAL_SHORT_COMMANDS=false -B -j4
|
||||
|
||||
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/shippedBinarys/ArchDep/arm/
|
||||
install -v -m755 libs/arm64-v8a/tun2socks $__dir/shippedBinarys/ArchDep/arm64/
|
||||
install -v -m755 libs/x86/tun2socks $__dir/shippedBinarys/ArchDep/386/
|
||||
install -v -m755 libs/x86_64/tun2socks $__dir/shippedBinarys/ArchDep/amd64/
|
||||
install -v -m755 libs/armeabi-v7a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
|
||||
install -v -m755 libs/arm64-v8a/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
|
||||
install -v -m755 libs/x86/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
|
||||
install -v -m755 libs/x86_64/tun2socks $__dir/../V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
|
||||
popd
|
||||
|
||||
pushd $__dir/shippedBinarys
|
||||
make clean && make shippedBinary
|
||||
popd
|
||||
|
||||
rm -rf $TMPDIR
|
||||
rm -rf $TMPDIR
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
||||
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
|
||||
"github.com/2dust/AndroidLibV2rayLite/VPN"
|
||||
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
|
||||
mobasset "golang.org/x/mobile/asset"
|
||||
|
||||
v2core "v2ray.com/core"
|
||||
@@ -49,6 +48,7 @@ type V2RayPoint struct {
|
||||
ConfigureFileContent string
|
||||
EnableLocalDNS bool
|
||||
ForwardIpv6 bool
|
||||
ProxyOnly bool
|
||||
}
|
||||
|
||||
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
|
||||
@@ -58,7 +58,6 @@ type V2RayVPNServiceSupportsSet interface {
|
||||
Shutdown() int
|
||||
Protect(int) int
|
||||
OnEmitStatus(int, string) int
|
||||
SendFd() int
|
||||
}
|
||||
|
||||
/*RunLoop Run V2Ray main loop
|
||||
@@ -82,7 +81,6 @@ func (v *V2RayPoint) RunLoop() (err error) {
|
||||
if !v.dialer.IsVServerReady() {
|
||||
log.Println("vServer cannot resolved, shutdown")
|
||||
v.StopLoop()
|
||||
v.SupportSet.Shutdown()
|
||||
}
|
||||
|
||||
// stop waiting if manually closed
|
||||
@@ -131,19 +129,10 @@ func (v *V2RayPoint) shutdownInit() {
|
||||
v.status.Vpoint = nil
|
||||
v.statsManager = nil
|
||||
v.escorter.EscortingDown()
|
||||
v.SupportSet.Shutdown()
|
||||
}
|
||||
|
||||
func (v *V2RayPoint) pointloop() error {
|
||||
if err := v.runTun2socks(); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
|
||||
v.EnableLocalDNS,
|
||||
v.ForwardIpv6,
|
||||
v.DomainName)
|
||||
|
||||
log.Println("loading v2ray config")
|
||||
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
|
||||
if err != nil {
|
||||
@@ -171,6 +160,19 @@ func (v *V2RayPoint) pointloop() error {
|
||||
v.SupportSet.Prepare()
|
||||
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
|
||||
v.SupportSet.OnEmitStatus(0, "Running")
|
||||
|
||||
if !v.ProxyOnly {
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -227,17 +229,10 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
|
||||
}
|
||||
|
||||
func (v V2RayPoint) runTun2socks() error {
|
||||
shipb := shippedBinarys.FirstRun{Status: v.status}
|
||||
if err := shipb.CheckAndExport(); err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
v.escorter.EscortingUp()
|
||||
go v.escorter.EscortRun(
|
||||
v.status.GetApp("libtun2socks.so"),
|
||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
|
||||
v.SupportSet.SendFd)
|
||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,13 +0,0 @@
|
||||
Platdep=shippedBinary.386 shippedBinary.amd64 shippedBinary.arm64 shippedBinary.arm
|
||||
|
||||
shippedBinaryDep:
|
||||
go get -u github.com/jteeuwen/go-bindata/...
|
||||
|
||||
shippedBinary.%:
|
||||
go-bindata -nometadata -nomemcopy -pkg shippedBinarys -o ./binary_$*.go -tags $* ArchIndep/ ArchDep/$*/
|
||||
|
||||
shippedBinary:shippedBinaryDep $(Platdep)
|
||||
@echo "Done"
|
||||
|
||||
clean:
|
||||
-rm binary*
|
||||
@@ -1,69 +0,0 @@
|
||||
package shippedBinarys
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/2dust/AndroidLibV2rayLite/CoreI"
|
||||
)
|
||||
|
||||
type FirstRun struct {
|
||||
Status *CoreI.Status
|
||||
}
|
||||
|
||||
func (v *FirstRun) checkIfRcExist() error {
|
||||
datadir := v.Status.GetDataDir()
|
||||
if _, err := os.Stat(datadir + strconv.Itoa(CoreI.CheckVersion())); !os.IsNotExist(err) {
|
||||
log.Println("file exists")
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
IndepDir, err := AssetDir("ArchIndep")
|
||||
log.Println(IndepDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fn := range IndepDir {
|
||||
log.Println(datadir+"ArchIndep/"+fn)
|
||||
|
||||
err := RestoreAsset(datadir, "ArchIndep/"+fn)
|
||||
log.Println(err)
|
||||
|
||||
//GrantPremission
|
||||
os.Chmod(datadir+"ArchIndep/"+fn, 0700)
|
||||
log.Println(os.Remove(datadir + fn))
|
||||
log.Println(os.Symlink(datadir+"ArchIndep/"+fn, datadir + fn))
|
||||
}
|
||||
|
||||
|
||||
DepDir, err := AssetDir("ArchDep")
|
||||
log.Println(DepDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fn := range DepDir {
|
||||
DepDir2, err := AssetDir("ArchDep/" + fn)
|
||||
log.Println("ArchDep/" + fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, FND := range DepDir2 {
|
||||
log.Println(datadir+"ArchDep/"+fn+"/"+FND)
|
||||
|
||||
RestoreAsset(datadir, "ArchDep/"+fn+"/"+FND)
|
||||
os.Chmod(datadir+"ArchDep/"+fn+"/"+FND, 0700)
|
||||
log.Println(os.Remove(datadir + FND))
|
||||
log.Println(os.Symlink(datadir+"ArchDep/"+fn+"/"+FND, datadir+FND))
|
||||
}
|
||||
}
|
||||
s, _ := os.Create(datadir + strconv.Itoa(CoreI.CheckVersion()))
|
||||
s.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *FirstRun) CheckAndExport() error {
|
||||
return v.checkIfRcExist()
|
||||
}
|
||||
29
README.md
29
README.md
@@ -1,5 +1,34 @@
|
||||
# v2rayNG
|
||||
|
||||
A V2Ray client for Android
|
||||
|
||||
[](https://developer.android.com/about/versions/jelly-bean#android-4.2)
|
||||
[](https://kotlinlang.org)
|
||||
[](https://github.com/2dust/v2rayNG/commits/master)
|
||||
[](https://www.codefactor.io/repository/github/2dust/v2rayng)
|
||||
[](https://github.com/2dust/v2rayNG/releases)
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
|
||||
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />
|
||||
</a>
|
||||
|
||||
### Usage
|
||||
|
||||
#### Geoip and Geosite
|
||||
v2rayNG release already embedded domain file `geoip.dat` and `geosite.dat`. However it is (probably) not the latest and not the most complete list.
|
||||
For power user, the embedded files can be easily replaced with the following steps:
|
||||
1. Launch v2rayNG (v1.4.9+)
|
||||
2. Find existing geoip.dat and geosite.dat in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
|
||||
3. Replace them with the latest [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip)
|
||||
4. Enhanced version can be found in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (recommend to use `geosite:geolocation-!cn` for proxy dns and routing)
|
||||
5. It is also possible to use third party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
|
||||
|
||||
#### See more in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
|
||||
### Development guide
|
||||
|
||||
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
|
||||
The aar can be compiled from the Golang project under AndroidLibV2rayLite folder. For a quick start, read guide for [Go Mobile](https://github.com/golang/go/wiki/Mobile)
|
||||
and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)
|
||||
|
||||
v2rayNG can run on Android Emulators, with minimum Android 5.0
|
||||
|
||||
@@ -62,14 +62,13 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
implementation project(':dpreference')
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2" // 1.3.x has compile error:
|
||||
// More than one file was found with OS independent path 'META-INF/proguard/coroutines.pro'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||
|
||||
// Android support library
|
||||
implementation "com.android.support:support-v4:$supportLibVersion"
|
||||
@@ -79,22 +78,16 @@ dependencies {
|
||||
implementation "com.android.support:preference-v7:$supportLibVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
|
||||
implementation "com.android.support:multidex:1.0.3"
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
|
||||
|
||||
// DSL
|
||||
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'io.reactivex:rxjava:1.3.4'
|
||||
implementation 'io.reactivex:rxandroid:1.2.1'
|
||||
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
|
||||
implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
|
||||
implementation 'io.reactivex:rxkotlin:0.60.0'
|
||||
implementation 'me.dm7.barcodescanner:core:1.9.8'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
|
||||
implementation 'com.beust:klaxon:3.0.1'
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
implementation(name: 'libv2ray', ext: 'aar')
|
||||
//implementation(name: 'tun2socks', ext: 'aar')
|
||||
|
||||
3
V2rayNG/app/proguard-rules.pro
vendored
3
V2rayNG/app/proguard-rules.pro
vendored
@@ -44,9 +44,6 @@
|
||||
static void throwUninitializedPropertyAccessException(java.lang.String);
|
||||
}
|
||||
|
||||
-dontwarn org.jetbrains.anko.internals.**
|
||||
-keep class org.jetbrains.anko.internals.** { *;}
|
||||
|
||||
-dontwarn rx.internal.util.unsafe.**
|
||||
-keep class rx.internal.util.unsafe.** { *;}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.v2ray.ang">
|
||||
|
||||
<supports-screens
|
||||
@@ -12,6 +13,11 @@
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
|
||||
|
||||
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@@ -21,12 +27,14 @@
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
|
||||
|
||||
<application
|
||||
android:name=".AngApplication"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:extractNativeLibs="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
@@ -34,20 +42,16 @@
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
@@ -79,6 +83,7 @@
|
||||
<activity
|
||||
android:name=".ui.ScSwitchActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:process=":RunSoLibV2RayDaemon"
|
||||
android:theme="@style/AppTheme.NoActionBar.Translucent" />
|
||||
|
||||
<service
|
||||
@@ -96,6 +101,12 @@
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<service android:name=".service.V2RayProxyOnlyService"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
</service>
|
||||
|
||||
<receiver android:name=".receiver.WidgetProvider"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<meta-data
|
||||
@@ -104,14 +115,16 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="com.v2ray.ang.action.widget.click" />
|
||||
<action android:name="com.v2ray.ang.action.activity" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".service.QSTileService"
|
||||
android:icon="@drawable/ic_v"
|
||||
android:label="@string/app_tile_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
android:name=".service.QSTileService"
|
||||
android:icon="@drawable/ic_v"
|
||||
android:label="@string/app_tile_name"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
@@ -126,7 +139,8 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receiver.TaskerReceiver">
|
||||
<receiver android:name=".receiver.TaskerReceiver"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<intent-filter>
|
||||
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package com.v2ray.ang
|
||||
|
||||
//import com.squareup.leakcanary.LeakCanary
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import android.support.v7.preference.PreferenceManager
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.jetbrains.anko.defaultSharedPreferences
|
||||
|
||||
class AngApplication : MultiDexApplication() {
|
||||
companion object {
|
||||
@@ -22,6 +21,7 @@ class AngApplication : MultiDexApplication() {
|
||||
|
||||
// LeakCanary.install(this)
|
||||
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
firstRun = defaultSharedPreferences.getInt(PREF_LAST_VERSION, 0) != BuildConfig.VERSION_CODE
|
||||
if (firstRun)
|
||||
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
|
||||
|
||||
@@ -13,6 +13,8 @@ object AppConfig {
|
||||
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
|
||||
const val PREF_CURR_CONFIG_OUTBOUND_TAGS = "pref_v2ray_config_outbound_tags"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val PREF_MODE = "pref_mode"
|
||||
|
||||
const val VMESS_PROTOCOL: String = "vmess://"
|
||||
const val SS_PROTOCOL: String = "ss://"
|
||||
const val SOCKS_PROTOCOL: String = "socks://"
|
||||
@@ -36,6 +38,7 @@ object AppConfig {
|
||||
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
|
||||
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
|
||||
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
|
||||
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
|
||||
const val promotionUrl = "https://1.2345345.xyz/ads.html"
|
||||
|
||||
const val DNS_AGENT = "1.1.1.1"
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.v2ray.ang.extension
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import com.v2ray.ang.AngApplication
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.json.JSONObject
|
||||
@@ -17,6 +18,17 @@ val Context.v2RayApplication: AngApplication
|
||||
val Context.defaultDPreference: DPreference
|
||||
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(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.content.Intent
|
||||
import android.widget.RemoteViews
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
class WidgetProvider : AppWidgetProvider() {
|
||||
@@ -17,21 +18,19 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
*/
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
|
||||
val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")
|
||||
updateWidgetBackground(context, appWidgetManager, appWidgetIds, isRunning)
|
||||
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
|
||||
}
|
||||
|
||||
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
|
||||
val intent = Intent(context, WidgetProvider::class.java)
|
||||
intent.setAction(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
|
||||
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||
if (isRunning) {
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme);
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme)
|
||||
} else {
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_grey);
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_grey)
|
||||
}
|
||||
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
@@ -40,23 +39,28 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收窗口小部件点击时发送的广播
|
||||
* 接收窗口小部件发送的广播
|
||||
*/
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
super.onReceive(context, intent)
|
||||
if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) {
|
||||
|
||||
val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")
|
||||
if (isRunning) {
|
||||
// context.toast(R.string.toast_services_stop)
|
||||
if (V2RayServiceManager.v2rayPoint.isRunning) {
|
||||
Utils.stopVService(context)
|
||||
} else {
|
||||
// context.toast(R.string.toast_services_start)
|
||||
Utils.startVService(context)
|
||||
Utils.startVServiceFromToggle(context)
|
||||
}
|
||||
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
!isRunning);
|
||||
when (intent.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
true)
|
||||
}
|
||||
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
|
||||
false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.drawable.Icon
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
@@ -15,7 +14,6 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.jetbrains.anko.toast
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
|
||||
@@ -33,7 +31,6 @@ class QSTileService : TileService() {
|
||||
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
|
||||
}
|
||||
|
||||
|
||||
qsTile?.updateTile()
|
||||
}
|
||||
|
||||
@@ -56,11 +53,7 @@ class QSTileService : TileService() {
|
||||
super.onClick()
|
||||
when (qsTile.state) {
|
||||
Tile.STATE_INACTIVE -> {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null)
|
||||
if (!Utils.startVService(this)) {
|
||||
toast(R.string.app_tile_first_use)
|
||||
}
|
||||
Utils.startVServiceFromToggle(this)
|
||||
}
|
||||
Tile.STATE_ACTIVE -> {
|
||||
Utils.stopVService(this)
|
||||
@@ -93,4 +86,4 @@ class QSTileService : TileService() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.app.Service
|
||||
|
||||
interface ServiceControl {
|
||||
fun getService(): Service
|
||||
|
||||
fun startService(parameters: String)
|
||||
|
||||
fun stopService()
|
||||
|
||||
fun vpnProtect(socket: Int): Boolean
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
class V2RayProxyOnlyService : Service(), ServiceControl {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
V2RayServiceManager.serviceControl = SoftReference(this)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
V2RayServiceManager.startV2rayPoint()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
V2RayServiceManager.stopV2rayPoint()
|
||||
}
|
||||
|
||||
override fun getService(): Service {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun startService(parameters: String) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun vpnProtect(socket: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.extension.toSpeedString
|
||||
import com.v2ray.ang.ui.MainActivity
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import go.Seq
|
||||
import libv2ray.Libv2ray
|
||||
import libv2ray.V2RayPoint
|
||||
import libv2ray.V2RayVPNServiceSupportsSet
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import java.lang.ref.SoftReference
|
||||
import kotlin.math.min
|
||||
|
||||
object V2RayServiceManager {
|
||||
private const val NOTIFICATION_ID = 1
|
||||
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
|
||||
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
|
||||
private const val NOTIFICATION_ICON_THRESHOLD = 3000
|
||||
|
||||
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
|
||||
private val mMsgReceive = ReceiveMessageHandler()
|
||||
|
||||
var serviceControl: SoftReference<ServiceControl>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
val context = value?.get()?.getService()?.applicationContext
|
||||
context?.let {
|
||||
v2rayPoint.packageName = Utils.packagePath(context)
|
||||
v2rayPoint.packageCodePath = context.applicationInfo.nativeLibraryDir + "/"
|
||||
Seq.setContext(context)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastQueryTime = 0L
|
||||
private var mBuilder: NotificationCompat.Builder? = null
|
||||
private var mSubscription: Subscription? = null
|
||||
private var mNotificationManager: NotificationManager? = null
|
||||
|
||||
fun startV2Ray(context: Context, mode: String) {
|
||||
val intent = if (mode == "VPN") {
|
||||
Intent(context.applicationContext, V2RayVpnService::class.java)
|
||||
} else {
|
||||
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private class V2RayCallback : V2RayVPNServiceSupportsSet {
|
||||
override fun shutdown(): Long {
|
||||
val serviceControl = serviceControl?.get() ?: return -1
|
||||
// called by go
|
||||
// shutdown the whole vpn service
|
||||
return try {
|
||||
serviceControl.stopService()
|
||||
0
|
||||
} catch (e: Exception) {
|
||||
Log.d(serviceControl.getService().packageName, e.toString())
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
override fun prepare(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun protect(l: Long): Long {
|
||||
val serviceControl = serviceControl?.get() ?: return 0
|
||||
return if (serviceControl.vpnProtect(l.toInt())) 0 else 1
|
||||
}
|
||||
|
||||
override fun onEmitStatus(l: Long, s: String?): Long {
|
||||
//Logger.d(s)
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun setup(s: String): Long {
|
||||
val serviceControl = serviceControl?.get() ?: return -1
|
||||
//Logger.d(s)
|
||||
return try {
|
||||
serviceControl.startService(s)
|
||||
lastQueryTime = System.currentTimeMillis()
|
||||
startSpeedNotification()
|
||||
0
|
||||
} catch (e: Exception) {
|
||||
Log.d(serviceControl.getService().packageName, e.toString())
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun startV2rayPoint() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
if (!v2rayPoint.isRunning) {
|
||||
|
||||
try {
|
||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
service.registerReceiver(mMsgReceive, mFilter)
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
}
|
||||
|
||||
v2rayPoint.configureFileContent = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
|
||||
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
|
||||
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
|
||||
v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN"
|
||||
|
||||
try {
|
||||
v2rayPoint.runLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
}
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||
showNotification()
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
|
||||
cancelNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopV2rayPoint() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
|
||||
cancelNotification()
|
||||
|
||||
try {
|
||||
service.unregisterReceiver(mMsgReceive)
|
||||
} catch (e: Exception) {
|
||||
Log.d(service.packageName, e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private class ReceiveMessageHandler : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
val serviceControl = serviceControl?.get() ?: return
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_REGISTER_CLIENT -> {
|
||||
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
|
||||
if (v2rayPoint.isRunning) {
|
||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
|
||||
}
|
||||
}
|
||||
AppConfig.MSG_UNREGISTER_CLIENT -> {
|
||||
// nothing to do
|
||||
}
|
||||
AppConfig.MSG_STATE_START -> {
|
||||
// nothing to do
|
||||
}
|
||||
AppConfig.MSG_STATE_STOP -> {
|
||||
serviceControl.stopService()
|
||||
}
|
||||
AppConfig.MSG_STATE_RESTART -> {
|
||||
startV2rayPoint()
|
||||
}
|
||||
}
|
||||
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
stopSpeedNotification()
|
||||
}
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
startSpeedNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
val startMainIntent = Intent(service, MainActivity::class.java)
|
||||
val contentPendingIntent = PendingIntent.getActivity(service,
|
||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
|
||||
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
||||
|
||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
|
||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
} else {
|
||||
// If earlier version channel ID is not used
|
||||
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||
""
|
||||
}
|
||||
|
||||
mBuilder = NotificationCompat.Builder(service, channelId)
|
||||
.setSmallIcon(R.drawable.ic_v)
|
||||
.setContentTitle(service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setOngoing(true)
|
||||
.setShowWhen(false)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.addAction(R.drawable.ic_close_grey_800_24dp,
|
||||
service.getString(R.string.notification_action_stop_v2ray),
|
||||
stopV2RayPendingIntent)
|
||||
//.build()
|
||||
|
||||
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
||||
|
||||
service.startForeground(NOTIFICATION_ID, mBuilder?.build())
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(): String {
|
||||
val channelId = "RAY_NG_M_CH_ID"
|
||||
val channelName = "V2rayNG Background Service"
|
||||
val chan = NotificationChannel(channelId,
|
||||
channelName, NotificationManager.IMPORTANCE_HIGH)
|
||||
chan.lightColor = Color.DKGRAY
|
||||
chan.importance = NotificationManager.IMPORTANCE_NONE
|
||||
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||
getNotificationManager()?.createNotificationChannel(chan)
|
||||
return channelId
|
||||
}
|
||||
|
||||
fun cancelNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
service.stopForeground(true)
|
||||
mBuilder = null
|
||||
mSubscription?.unsubscribe()
|
||||
mSubscription = null
|
||||
}
|
||||
|
||||
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
|
||||
if (mBuilder != null) {
|
||||
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_v)
|
||||
} else if (proxyTraffic > directTraffic) {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
|
||||
} else {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
|
||||
}
|
||||
mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
mBuilder?.setContentText(contentText) // Emui4.1 need content text even if style is set as BigTextStyle
|
||||
getNotificationManager()?.notify(NOTIFICATION_ID, mBuilder?.build())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNotificationManager(): NotificationManager? {
|
||||
if (mNotificationManager == null) {
|
||||
val service = serviceControl?.get()?.getService() ?: return null
|
||||
mNotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
}
|
||||
return mNotificationManager
|
||||
}
|
||||
|
||||
fun startSpeedNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
if (mSubscription == null &&
|
||||
v2rayPoint.isRunning &&
|
||||
service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
|
||||
var lastZeroSpeed = false
|
||||
val outboundTags = service.defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
|
||||
outboundTags.remove(TAG_DIRECT)
|
||||
|
||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.subscribe {
|
||||
val queryTime = System.currentTimeMillis()
|
||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||
var proxyTotal = 0L
|
||||
val text = StringBuilder()
|
||||
outboundTags.forEach {
|
||||
val up = v2rayPoint.queryStats(it, "uplink")
|
||||
val down = v2rayPoint.queryStats(it, "downlink")
|
||||
if (up + down > 0) {
|
||||
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
|
||||
proxyTotal += up + down
|
||||
}
|
||||
}
|
||||
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
|
||||
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
|
||||
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
|
||||
if (!zeroSpeed || !lastZeroSpeed) {
|
||||
if (proxyTotal == 0L) {
|
||||
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
|
||||
}
|
||||
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds)
|
||||
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
|
||||
}
|
||||
lastZeroSpeed = zeroSpeed
|
||||
lastQueryTime = queryTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
|
||||
var n = name ?: "no tag"
|
||||
n = n.substring(0, min(n.length, 6))
|
||||
text.append(n)
|
||||
for (i in n.length..6 step 2) {
|
||||
text.append("\t")
|
||||
}
|
||||
text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
|
||||
}
|
||||
|
||||
fun stopSpeedNotification() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
if (mSubscription != null) {
|
||||
mSubscription?.unsubscribe() //stop queryStats
|
||||
mSubscription = null
|
||||
|
||||
val cfName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
|
||||
updateNotification(cfName, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +1,29 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.net.*
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.StrictMode
|
||||
import android.support.annotation.RequiresApi
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.TAG_DIRECT
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.extension.toSpeedString
|
||||
import com.v2ray.ang.ui.MainActivity
|
||||
import com.v2ray.ang.ui.PerAppProxyActivity
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import go.Seq
|
||||
import libv2ray.Libv2ray
|
||||
import libv2ray.V2RayVPNServiceSupportsSet
|
||||
import org.jetbrains.anko.doAsync
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
class V2RayVpnService : VpnService() {
|
||||
companion object {
|
||||
const val NOTIFICATION_ID = 1
|
||||
const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
|
||||
const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
|
||||
const val NOTIFICATION_ICON_THRESHOLD = 3000
|
||||
|
||||
fun startV2Ray(context: Context) {
|
||||
val intent = Intent(context.applicationContext, V2RayVpnService::class.java)
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
|
||||
private var lastQueryTime = 0L
|
||||
private lateinit var configContent: String
|
||||
class V2RayVpnService : VpnService(), ServiceControl {
|
||||
private lateinit var mInterface: ParcelFileDescriptor
|
||||
val fd: Int get() = mInterface.fd
|
||||
private var mBuilder: NotificationCompat.Builder? = null
|
||||
private var mSubscription: Subscription? = null
|
||||
private var mNotificationManager: NotificationManager? = null
|
||||
|
||||
|
||||
/**
|
||||
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
|
||||
@@ -72,22 +34,23 @@ class V2RayVpnService : VpnService() {
|
||||
*
|
||||
* 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()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
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() {
|
||||
override fun onAvailable(network: Network) {
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||
// it's a good idea to refresh capabilities
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
@@ -96,16 +59,13 @@ class V2RayVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
}
|
||||
private var listeningForDefaultNetwork = false
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
|
||||
StrictMode.setThreadPolicy(policy)
|
||||
v2rayPoint.packageName = Utils.packagePath(applicationContext)
|
||||
v2rayPoint.packageCodePath = applicationContext.applicationInfo.nativeLibraryDir + "/"
|
||||
Seq.setContext(applicationContext)
|
||||
V2RayServiceManager.serviceControl = SoftReference(this)
|
||||
}
|
||||
|
||||
override fun onRevoke() {
|
||||
@@ -119,12 +79,12 @@ class V2RayVpnService : VpnService() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cancelNotification()
|
||||
stopV2Ray()
|
||||
}
|
||||
|
||||
fun setup(parameters: String) {
|
||||
private fun setup(parameters: String) {
|
||||
|
||||
val prepare = VpnService.prepare(this)
|
||||
val prepare = prepare(this)
|
||||
if (prepare != null) {
|
||||
return
|
||||
}
|
||||
@@ -147,8 +107,8 @@ class V2RayVpnService : VpnService() {
|
||||
if (it[1] == "::") { //not very elegant, should move Vpn setting in Kotlin, simplify go code
|
||||
builder.addRoute("2000::", 3)
|
||||
} else {
|
||||
resources.getStringArray(R.array.bypass_private_ip_address).forEach {
|
||||
val addr = it.split('/')
|
||||
resources.getStringArray(R.array.bypass_private_ip_address).forEach { cidr ->
|
||||
val addr = cidr.split('/')
|
||||
builder.addRoute(addr[0], addr[1].toInt())
|
||||
}
|
||||
}
|
||||
@@ -189,34 +149,42 @@ class V2RayVpnService : VpnService() {
|
||||
try {
|
||||
mInterface.close()
|
||||
} catch (ignored: Exception) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = true
|
||||
try {
|
||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
builder.setMetered(false)
|
||||
}
|
||||
|
||||
// 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()
|
||||
lastQueryTime = System.currentTimeMillis()
|
||||
startSpeedNotification()
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
stopV2Ray(true)
|
||||
}
|
||||
|
||||
fun sendFd() {
|
||||
private fun sendFd() {
|
||||
val fd = mInterface.fileDescriptor
|
||||
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
|
||||
|
||||
doAsync {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
var tries = 0
|
||||
while (true) try {
|
||||
Thread.sleep(50L shl tries)
|
||||
Log.d(packageName, "sendFd tries: " + tries.toString())
|
||||
Thread.sleep(1000L shl tries)
|
||||
Log.d(packageName, "sendFd tries: $tries")
|
||||
LocalSocket().use { localSocket ->
|
||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
||||
@@ -232,74 +200,27 @@ class V2RayVpnService : VpnService() {
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
startV2ray()
|
||||
V2RayServiceManager.startV2rayPoint()
|
||||
return START_STICKY
|
||||
//return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
private fun startV2ray() {
|
||||
if (!v2rayPoint.isRunning) {
|
||||
|
||||
try {
|
||||
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
mFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||
mFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
registerReceiver(mMsgReceive, mFilter)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
|
||||
v2rayPoint.configureFileContent = configContent
|
||||
v2rayPoint.enableLocalDNS = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
v2rayPoint.forwardIpv6 = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
|
||||
v2rayPoint.domainName = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
|
||||
|
||||
try {
|
||||
v2rayPoint.runLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
}
|
||||
|
||||
if (v2rayPoint.isRunning) {
|
||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "")
|
||||
showNotification()
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "")
|
||||
cancelNotification()
|
||||
}
|
||||
}
|
||||
// showNotification()
|
||||
}
|
||||
|
||||
private fun stopV2Ray(isForced: Boolean = true) {
|
||||
// val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "")
|
||||
// val emptyInfo = VpnNetworkInfo()
|
||||
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
|
||||
// saveVpnNetworkInfo(configName, info)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (listeningForDefaultNetwork) {
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = false
|
||||
}
|
||||
}
|
||||
if (v2rayPoint.isRunning) {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
} catch (ignored: Exception) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "")
|
||||
cancelNotification()
|
||||
V2RayServiceManager.stopV2rayPoint()
|
||||
|
||||
if (isForced) {
|
||||
try {
|
||||
unregisterReceiver(mMsgReceive)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
|
||||
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
|
||||
//It's strage but true.
|
||||
//This can be verified by putting stopself() behind and call stopLoop and startLoop
|
||||
@@ -310,243 +231,26 @@ class V2RayVpnService : VpnService() {
|
||||
try {
|
||||
mInterface.close()
|
||||
} catch (ignored: Exception) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNotification() {
|
||||
val startMainIntent = Intent(applicationContext, MainActivity::class.java)
|
||||
val contentPendingIntent = PendingIntent.getActivity(applicationContext,
|
||||
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
|
||||
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
|
||||
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
|
||||
|
||||
val stopV2RayPendingIntent = PendingIntent.getBroadcast(applicationContext,
|
||||
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createNotificationChannel()
|
||||
} else {
|
||||
// If earlier version channel ID is not used
|
||||
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||
""
|
||||
}
|
||||
|
||||
mBuilder = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setSmallIcon(R.drawable.ic_v)
|
||||
.setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||
.setOngoing(true)
|
||||
.setShowWhen(false)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setContentIntent(contentPendingIntent)
|
||||
.addAction(R.drawable.ic_close_grey_800_24dp,
|
||||
getString(R.string.notification_action_stop_v2ray),
|
||||
stopV2RayPendingIntent)
|
||||
//.build()
|
||||
|
||||
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
|
||||
|
||||
startForeground(NOTIFICATION_ID, mBuilder?.build())
|
||||
override fun getService(): Service {
|
||||
return this
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(): String {
|
||||
val channelId = "RAY_NG_M_CH_ID"
|
||||
val channelName = "V2rayNG Background Service"
|
||||
val chan = NotificationChannel(channelId,
|
||||
channelName, NotificationManager.IMPORTANCE_HIGH)
|
||||
chan.lightColor = Color.DKGRAY
|
||||
chan.importance = NotificationManager.IMPORTANCE_NONE
|
||||
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||
getNotificationManager().createNotificationChannel(chan)
|
||||
return channelId
|
||||
override fun startService(parameters: String) {
|
||||
setup(parameters)
|
||||
}
|
||||
|
||||
private fun cancelNotification() {
|
||||
stopForeground(true)
|
||||
mBuilder = null
|
||||
mSubscription?.unsubscribe()
|
||||
mSubscription = null
|
||||
override fun stopService() {
|
||||
stopV2Ray(true)
|
||||
}
|
||||
|
||||
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
|
||||
if (mBuilder != null) {
|
||||
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_v)
|
||||
} else if (proxyTraffic > directTraffic) {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
|
||||
} else {
|
||||
mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
|
||||
}
|
||||
mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
|
||||
mBuilder?.setContentText(contentText) // Emui4.1 need content text even if style is set as BigTextStyle
|
||||
getNotificationManager().notify(NOTIFICATION_ID, mBuilder?.build())
|
||||
}
|
||||
override fun vpnProtect(socket: Int): Boolean {
|
||||
return protect(socket)
|
||||
}
|
||||
|
||||
private fun getNotificationManager(): NotificationManager {
|
||||
if (mNotificationManager == null) {
|
||||
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
}
|
||||
return mNotificationManager!!
|
||||
}
|
||||
|
||||
fun startSpeedNotification() {
|
||||
if (mSubscription == null &&
|
||||
v2rayPoint.isRunning &&
|
||||
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
|
||||
var last_zero_speed = false
|
||||
val outboundTags = defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
|
||||
outboundTags.remove(TAG_DIRECT)
|
||||
|
||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.subscribe {
|
||||
val queryTime = System.currentTimeMillis()
|
||||
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
|
||||
var proxyTotal = 0L
|
||||
val text = StringBuilder()
|
||||
outboundTags.forEach {
|
||||
val up = v2rayPoint.queryStats(it, "uplink")
|
||||
val down = v2rayPoint.queryStats(it, "downlink")
|
||||
if (up + down > 0) {
|
||||
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
|
||||
proxyTotal += up + down
|
||||
}
|
||||
}
|
||||
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
|
||||
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
|
||||
val zero_speed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
|
||||
if (!zero_speed || !last_zero_speed) {
|
||||
if (proxyTotal == 0L) {
|
||||
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
|
||||
}
|
||||
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
|
||||
directDownlink / sinceLastQueryInSeconds)
|
||||
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
|
||||
}
|
||||
last_zero_speed = zero_speed
|
||||
lastQueryTime = queryTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
|
||||
var n = name ?: "no tag"
|
||||
n = n.substring(0, Math.min(n.length, 6))
|
||||
text.append(n)
|
||||
for (i in n.length..6 step 2) {
|
||||
text.append("\t")
|
||||
}
|
||||
text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
|
||||
}
|
||||
|
||||
fun stopSpeedNotification() {
|
||||
if (mSubscription != null) {
|
||||
mSubscription?.unsubscribe() //stop queryStats
|
||||
mSubscription = null
|
||||
|
||||
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
|
||||
updateNotification(cf_name, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class V2RayCallback : V2RayVPNServiceSupportsSet {
|
||||
override fun shutdown(): Long {
|
||||
// called by go
|
||||
// shutdown the whole vpn service
|
||||
try {
|
||||
this@V2RayVpnService.shutdown()
|
||||
return 0
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
override fun prepare(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun protect(l: Long) = (if (this@V2RayVpnService.protect(l.toInt())) 0 else 1).toLong()
|
||||
|
||||
override fun onEmitStatus(l: Long, s: String?): Long {
|
||||
//Logger.d(s)
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun setup(s: String): Long {
|
||||
//Logger.d(s)
|
||||
try {
|
||||
this@V2RayVpnService.setup(s)
|
||||
return 0
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendFd(): Long {
|
||||
try {
|
||||
this@V2RayVpnService.sendFd()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService)
|
||||
|
||||
private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() {
|
||||
internal var mReference: SoftReference<V2RayVpnService> = SoftReference(vpnService)
|
||||
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
val vpnService = mReference.get()
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_REGISTER_CLIENT -> {
|
||||
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
|
||||
|
||||
val isRunning = vpnService?.v2rayPoint!!.isRunning
|
||||
&& VpnService.prepare(vpnService) == null
|
||||
if (isRunning) {
|
||||
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "")
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "")
|
||||
}
|
||||
}
|
||||
AppConfig.MSG_UNREGISTER_CLIENT -> {
|
||||
// vpnService?.mMsgSend = null
|
||||
}
|
||||
AppConfig.MSG_STATE_START -> {
|
||||
//nothing to do
|
||||
}
|
||||
AppConfig.MSG_STATE_STOP -> {
|
||||
vpnService?.stopV2Ray()
|
||||
}
|
||||
AppConfig.MSG_STATE_RESTART -> {
|
||||
vpnService?.startV2ray()
|
||||
}
|
||||
}
|
||||
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
vpnService?.stopSpeedNotification()
|
||||
}
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
vpnService?.startSpeedNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,7 @@ import android.support.v7.widget.Toolbar
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
//import com.v2ray.ang.InappBuyActivity
|
||||
|
||||
import com.v2ray.ang.R
|
||||
import org.jetbrains.anko.startActivity
|
||||
|
||||
|
||||
abstract class BaseDrawerActivity : BaseActivity() {
|
||||
companion object {
|
||||
@@ -58,7 +54,7 @@ abstract class BaseDrawerActivity : BaseActivity() {
|
||||
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
|
||||
R.id.settings -> activityClass = SettingsActivity::class.java
|
||||
R.id.logcat -> {
|
||||
startActivity<LogcatActivity>()
|
||||
startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
|
||||
return
|
||||
}
|
||||
R.id.donate -> {
|
||||
|
||||
@@ -8,12 +8,12 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_logcat.*
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
@@ -34,7 +34,7 @@ class LogcatActivity : BaseActivity() {
|
||||
try {
|
||||
pb_waiting.visibility = View.VISIBLE
|
||||
|
||||
doAsync {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
if (shouldFlushLog) {
|
||||
val lst = LinkedHashSet<String>()
|
||||
lst.add("logcat")
|
||||
@@ -54,7 +54,7 @@ class LogcatActivity : BaseActivity() {
|
||||
// InputStreamReader(process.inputStream))
|
||||
// val allText = bufferedReader.use(BufferedReader::readText)
|
||||
val allText = process.inputStream.bufferedReader().use { it.readText() }
|
||||
uiThread {
|
||||
launch(Dispatchers.Main) {
|
||||
tv_logcat.text = allText
|
||||
tv_logcat.movementMethod = ScrollingMovementMethod()
|
||||
pb_waiting.visibility = View.GONE
|
||||
|
||||
@@ -18,7 +18,6 @@ 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
|
||||
@@ -27,14 +26,17 @@ import android.support.v4.view.GravityCompat
|
||||
import android.support.v7.app.ActionBarDrawerToggle
|
||||
import android.support.v7.widget.helper.ItemTouchHelper
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.BuildConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
//import com.v2ray.ang.InappBuyActivity
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.extension.toast
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
|
||||
import com.v2ray.ang.util.AngConfigManager.configs
|
||||
import kotlinx.coroutines.*
|
||||
import libv2ray.Libv2ray
|
||||
|
||||
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||
companion object {
|
||||
@@ -49,10 +51,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
field = value
|
||||
adapter.changeable = !value
|
||||
if (value) {
|
||||
fab.imageResource = R.drawable.ic_v
|
||||
fab.setImageResource(R.drawable.ic_v)
|
||||
tv_test_state.text = getString(R.string.connection_connected)
|
||||
} else {
|
||||
fab.imageResource = R.drawable.ic_v_idle
|
||||
fab.setImageResource(R.drawable.ic_v_idle)
|
||||
tv_test_state.text = getString(R.string.connection_not_connected)
|
||||
}
|
||||
hideCircle()
|
||||
@@ -60,7 +62,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
|
||||
private val adapter by lazy { MainRecyclerAdapter(this) }
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
private val testingJobs = ArrayList<Job>()
|
||||
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -71,13 +73,15 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
fab.setOnClickListener {
|
||||
if (isRunning) {
|
||||
Utils.stopVService(this)
|
||||
} else {
|
||||
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null) {
|
||||
startV2Ray()
|
||||
} else {
|
||||
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
|
||||
}
|
||||
} else {
|
||||
startV2Ray()
|
||||
}
|
||||
}
|
||||
layout_test.setOnClickListener {
|
||||
@@ -85,9 +89,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
|
||||
|
||||
tv_test_state.text = getString(R.string.connection_test_testing)
|
||||
doAsync {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val result = Utils.testConnection(this@MainActivity, socksPort)
|
||||
uiThread {
|
||||
launch(Dispatchers.Main) {
|
||||
tv_test_state.text = Utils.getEditable(result)
|
||||
}
|
||||
}
|
||||
@@ -110,6 +114,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
drawer_layout.addDrawerListener(toggle)
|
||||
toggle.syncState()
|
||||
nav_view.setNavigationItemSelectedListener(this)
|
||||
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
|
||||
}
|
||||
|
||||
fun startV2Ray() {
|
||||
@@ -182,6 +187,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
return true
|
||||
}
|
||||
|
||||
private fun getOptionIntent() = Intent().putExtra("position", -1)
|
||||
.putExtra("isRunning", isRunning)
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.import_qrcode -> {
|
||||
importQRcode(REQUEST_SCAN)
|
||||
@@ -192,17 +200,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
true
|
||||
}
|
||||
R.id.import_manually_vmess -> {
|
||||
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning)
|
||||
startActivity(getOptionIntent().setClass(this, ServerActivity::class.java))
|
||||
adapter.updateConfigList()
|
||||
true
|
||||
}
|
||||
R.id.import_manually_ss -> {
|
||||
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning)
|
||||
startActivity(getOptionIntent().setClass(this, Server3Activity::class.java))
|
||||
adapter.updateConfigList()
|
||||
true
|
||||
}
|
||||
R.id.import_manually_socks -> {
|
||||
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning)
|
||||
startActivity(getOptionIntent().setClass(this, Server4Activity::class.java))
|
||||
adapter.updateConfigList()
|
||||
true
|
||||
}
|
||||
@@ -243,10 +251,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
R.id.ping_all -> {
|
||||
testingJobs.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
testingJobs.clear()
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
Utils.closeAllTcpSockets()
|
||||
for (k in 0 until configs.vmess.count()) {
|
||||
configs.vmess[k].testResult = ""
|
||||
@@ -261,16 +266,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
serverAddress = serverOutbound.getServerAddress() ?: continue
|
||||
serverPort = serverOutbound.getServerPort() ?: continue
|
||||
}
|
||||
testingJobs.add(GlobalScope.launch(Dispatchers.IO) {
|
||||
tcpingTestScope.launch {
|
||||
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
|
||||
}
|
||||
@@ -300,7 +303,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
startActivityForResult<ScannerActivity>(requestCode)
|
||||
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
|
||||
else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
@@ -386,9 +389,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
toast(R.string.toast_invalid_url)
|
||||
return false
|
||||
}
|
||||
doAsync {
|
||||
val configText = URL(url).readText()
|
||||
uiThread {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val configText = try {
|
||||
URL(url).readText()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
importCustomizeConfig(configText)
|
||||
}
|
||||
}
|
||||
@@ -420,9 +428,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
continue
|
||||
}
|
||||
Log.d("Main", url)
|
||||
doAsync {
|
||||
val configText = URL(url).readText()
|
||||
uiThread {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val configText = try {
|
||||
URL(url).readText()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
importBatchConfig(Utils.decode(configText), id)
|
||||
}
|
||||
}
|
||||
@@ -568,10 +581,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
when (item.itemId) {
|
||||
//R.id.server_profile -> activityClass = MainActivity::class.java
|
||||
R.id.sub_setting -> {
|
||||
startActivity<SubSettingActivity>()
|
||||
startActivity(Intent(this, SubSettingActivity::class.java))
|
||||
}
|
||||
R.id.settings -> {
|
||||
startActivity<SettingsActivity>("isRunning" to isRunning)
|
||||
startActivity(Intent(this, SettingsActivity::class.java)
|
||||
.putExtra("isRunning", isRunning))
|
||||
}
|
||||
R.id.feedback -> {
|
||||
Utils.openUri(this, AppConfig.v2rayNGIssues)
|
||||
@@ -583,7 +597,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
// startActivity<InappBuyActivity>()
|
||||
}
|
||||
R.id.logcat -> {
|
||||
startActivity<LogcatActivity>()
|
||||
startActivity(Intent(this, LogcatActivity::class.java))
|
||||
}
|
||||
}
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
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.ItemTouchHelperViewHolder
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
@@ -17,7 +20,6 @@ import com.v2ray.ang.util.Utils
|
||||
import com.v2ray.ang.util.V2rayConfigUtil
|
||||
import kotlinx.android.synthetic.main.item_qrcode.view.*
|
||||
import kotlinx.android.synthetic.main.item_recycler_main.view.*
|
||||
import org.jetbrains.anko.*
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -60,7 +62,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
|
||||
holder.name.text = remarks
|
||||
holder.radio.isChecked = (position == configs.index)
|
||||
holder.itemView.backgroundColor = Color.TRANSPARENT
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
holder.test_result.text = test_result
|
||||
|
||||
if (TextUtils.isEmpty(subid)) {
|
||||
@@ -85,7 +87,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
holder.layout_share.setOnClickListener {
|
||||
mActivity.selector(null, shareOptions) { dialogInterface, i ->
|
||||
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
|
||||
try {
|
||||
when (i) {
|
||||
0 -> {
|
||||
@@ -94,14 +96,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
} else {
|
||||
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
|
||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
|
||||
|
||||
mActivity.alert {
|
||||
customView {
|
||||
linearLayout {
|
||||
addView(iv)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
AlertDialog.Builder(mActivity).setView(iv).show()
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
@@ -117,18 +112,20 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
val intent = Intent().putExtra("position", position)
|
||||
.putExtra("isRunning", !changeable)
|
||||
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) {
|
||||
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
|
||||
mActivity.startActivity(intent.setClass(mActivity, Server2Activity::class.java))
|
||||
} 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) {
|
||||
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
|
||||
mActivity.startActivity(intent.setClass(mActivity, Server4Activity::class.java))
|
||||
}
|
||||
}
|
||||
holder.layout_remove.setOnClickListener {
|
||||
@@ -183,10 +180,10 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||
when (viewType) {
|
||||
VIEW_TYPE_ITEM ->
|
||||
return MainViewHolder(parent.context.layoutInflater
|
||||
return MainViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_recycler_main, parent, false))
|
||||
else ->
|
||||
return FooterViewHolder(parent.context.layoutInflater
|
||||
return FooterViewHolder(LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_recycler_footer, parent, false))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.support.v7.widget.DividerItemDecoration
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
@@ -12,7 +14,6 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import com.dinuscxj.itemdecoration.LinearDividerItemDecoration
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.AppManagerUtil
|
||||
@@ -25,11 +26,12 @@ import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
class PerAppProxyActivity : BaseActivity() {
|
||||
@@ -47,8 +49,7 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
val dividerItemDecoration = LinearDividerItemDecoration(
|
||||
this, LinearDividerItemDecoration.LINEAR_DIVIDER_VERTICAL)
|
||||
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
|
||||
recycler_view.addItemDecoration(dividerItemDecoration)
|
||||
|
||||
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
|
||||
@@ -220,9 +221,14 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
private fun selectProxyApp() {
|
||||
toast(R.string.msg_downloading_content)
|
||||
val url = AppConfig.androidpackagenamelistUrl
|
||||
doAsync {
|
||||
val content = URL(url).readText()
|
||||
uiThread {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val content = try {
|
||||
URL(url).readText()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
Log.d("selectProxyApp", content)
|
||||
selectProxyApp(content)
|
||||
toast(R.string.toast_success)
|
||||
@@ -276,4 +282,4 @@ class PerAppProxyActivity : BaseActivity() {
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,12 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.graphics.Color
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AppInfo
|
||||
import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.*
|
||||
import org.jetbrains.anko.image
|
||||
import org.jetbrains.anko.layoutInflater
|
||||
import org.jetbrains.anko.textColor
|
||||
import java.util.*
|
||||
|
||||
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
|
||||
@@ -45,7 +43,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
// VIEW_TYPE_ITEM -> AppViewHolder(ctx.layoutInflater
|
||||
// .inflate(R.layout.item_recycler_bypass_list, parent, false))
|
||||
|
||||
else -> AppViewHolder(ctx.layoutInflater
|
||||
else -> AppViewHolder(LayoutInflater.from(ctx)
|
||||
.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) {
|
||||
this.appInfo = appInfo
|
||||
|
||||
icon.image = appInfo.appIcon
|
||||
icon.setImageDrawable(appInfo.appIcon)
|
||||
// name.text = appInfo.appName
|
||||
|
||||
checkBox.isChecked = inBlacklist
|
||||
package_name.text = appInfo.packageName
|
||||
if (appInfo.isSystemApp) {
|
||||
name.text = String.format("** %1s", appInfo.appName)
|
||||
name.textColor = Color.RED
|
||||
name.setTextColor(Color.RED)
|
||||
} else {
|
||||
name.text = appInfo.appName
|
||||
name.textColor = Color.DKGRAY
|
||||
name.setTextColor(Color.DKGRAY)
|
||||
}
|
||||
|
||||
itemView.setOnClickListener(this)
|
||||
@@ -94,4 +92,4 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.Fragment
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.fragment_routing_settings.*
|
||||
import org.jetbrains.anko.toast
|
||||
import android.view.MenuInflater
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.AppConfig
|
||||
import org.jetbrains.anko.doAsync
|
||||
import org.jetbrains.anko.startActivityForResult
|
||||
import org.jetbrains.anko.support.v4.startActivityForResult
|
||||
import org.jetbrains.anko.support.v4.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import com.v2ray.ang.extension.toast
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URL
|
||||
|
||||
|
||||
class RoutingSettingsFragment : Fragment() {
|
||||
companion object {
|
||||
private const val routing_arg = "routing_arg"
|
||||
@@ -96,7 +90,7 @@ class RoutingSettingsFragment : Fragment() {
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
startActivityForResult<ScannerActivity>(requestCode)
|
||||
startActivityForResult(Intent(activity, ScannerActivity::class.java), requestCode)
|
||||
else
|
||||
activity?.toast(R.string.toast_permission_denied)
|
||||
}
|
||||
@@ -118,12 +112,17 @@ class RoutingSettingsFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
toast(R.string.msg_downloading_content)
|
||||
doAsync {
|
||||
val content = URL(url).readText()
|
||||
uiThread {
|
||||
et_routing_content.text = Utils.getEditable(content!!)
|
||||
toast(R.string.toast_success)
|
||||
activity?.toast(R.string.msg_downloading_content)
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
val content = try {
|
||||
URL(url).readText()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
""
|
||||
}
|
||||
launch(Dispatchers.Main) {
|
||||
et_routing_content.text = Utils.getEditable(content)
|
||||
activity?.toast(R.string.toast_success)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import android.os.Bundle
|
||||
import org.jetbrains.anko.*
|
||||
import com.v2ray.ang.extension.toast
|
||||
|
||||
class ScScannerActivity : BaseActivity() {
|
||||
companion object {
|
||||
@@ -24,7 +24,7 @@ class ScScannerActivity : BaseActivity() {
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.subscribe {
|
||||
if (it)
|
||||
startActivityForResult<ScannerActivity>(requestCode)
|
||||
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
|
||||
else
|
||||
toast(R.string.toast_permission_denied)
|
||||
}
|
||||
@@ -43,10 +43,10 @@ class ScScannerActivity : BaseActivity() {
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
startActivity<MainActivity>()
|
||||
startActivity(Intent(this, MainActivity::class.java))
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,104 +1,22 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.*
|
||||
import android.net.VpnService
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.util.Utils
|
||||
import android.os.Bundle
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import java.lang.ref.SoftReference
|
||||
import android.content.IntentFilter
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import java.util.concurrent.TimeUnit
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
|
||||
class ScSwitchActivity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_CODE_VPN_PREPARE = 0
|
||||
}
|
||||
|
||||
var isRunning = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
Utils.stopVService(this)
|
||||
} else {
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent == null) {
|
||||
Utils.startVService(this)
|
||||
} else {
|
||||
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
|
||||
}
|
||||
}
|
||||
finishActivity()
|
||||
}
|
||||
|
||||
fun finishActivity() {
|
||||
try {
|
||||
Observable.timer(5000, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
finish()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
moveTaskToBack(true)
|
||||
|
||||
setContentView(R.layout.activity_none)
|
||||
|
||||
val isRunning = Utils.isServiceRun(this, "com.v2ray.ang.service.V2RayVpnService")
|
||||
if (isRunning) {
|
||||
//Utils.stopVService(this)
|
||||
mMsgReceive = ReceiveMessageHandler(this@ScSwitchActivity)
|
||||
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
|
||||
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
|
||||
|
||||
if (V2RayServiceManager.v2rayPoint.isRunning) {
|
||||
Utils.stopVService(this)
|
||||
} else {
|
||||
Utils.startVService(this)
|
||||
finishActivity()
|
||||
Utils.startVServiceFromToggle(this)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
if (mMsgReceive != null) {
|
||||
unregisterReceiver(mMsgReceive)
|
||||
mMsgReceive = null
|
||||
}
|
||||
}
|
||||
|
||||
private var mMsgReceive: BroadcastReceiver? = null
|
||||
|
||||
private class ReceiveMessageHandler(activity: ScSwitchActivity) : BroadcastReceiver() {
|
||||
internal var mReference: SoftReference<ScSwitchActivity> = SoftReference(activity)
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
val activity = mReference.get()
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_STATE_RUNNING -> {
|
||||
activity?.isRunning = true
|
||||
}
|
||||
AppConfig.MSG_STATE_NOT_RUNNING -> {
|
||||
activity?.isRunning = false
|
||||
}
|
||||
// AppConfig.MSG_STATE_START_SUCCESS -> {
|
||||
// activity?.toast(R.string.toast_services_success)
|
||||
// activity?.isRunning = true
|
||||
// }
|
||||
// AppConfig.MSG_STATE_START_FAILURE -> {
|
||||
// activity?.toast(R.string.toast_services_failure)
|
||||
// activity?.isRunning = false
|
||||
// }
|
||||
// AppConfig.MSG_STATE_STOP_SUCCESS -> {
|
||||
// activity?.isRunning = false
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,13 @@ import com.google.zxing.Result
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.icu.util.TimeUnit
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.tbruyelle.rxpermissions.RxPermissions
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.QRCodeDecoder
|
||||
import org.jetbrains.anko.toast
|
||||
import rx.Observable
|
||||
import android.os.SystemClock
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import rx.Observer
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import javax.xml.datatype.DatatypeConstants.SECONDS
|
||||
|
||||
|
||||
|
||||
|
||||
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
companion object {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
@@ -10,13 +11,12 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server2.*
|
||||
import org.jetbrains.anko.*
|
||||
import java.lang.Exception
|
||||
|
||||
|
||||
class Server2Activity : BaseActivity() {
|
||||
companion object {
|
||||
private const val REQUEST_SCAN = 1
|
||||
@@ -112,17 +112,16 @@ class Server2Activity : BaseActivity() {
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
alert(R.string.del_config_comfirm) {
|
||||
positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
@@ -158,4 +157,4 @@ class Server2Activity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server3.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
|
||||
class Server3Activity : BaseActivity() {
|
||||
companion object {
|
||||
@@ -126,17 +126,16 @@ class Server3Activity : BaseActivity() {
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
alert(R.string.del_config_comfirm) {
|
||||
positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
@@ -172,4 +171,4 @@ class Server3Activity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server4.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
|
||||
class Server4Activity : BaseActivity() {
|
||||
companion object {
|
||||
@@ -110,17 +110,16 @@ class Server4Activity : BaseActivity() {
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
alert(R.string.del_config_comfirm) {
|
||||
positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
@@ -156,4 +155,4 @@ class Server4Activity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_server.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
|
||||
class ServerActivity : BaseActivity() {
|
||||
companion object {
|
||||
@@ -169,17 +169,16 @@ class ServerActivity : BaseActivity() {
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
alert(R.string.del_config_comfirm) {
|
||||
positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeServer(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
@@ -215,4 +214,4 @@ class ServerActivity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.preference.*
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.BuildConfig
|
||||
//import com.v2ray.ang.InappBuyActivity
|
||||
import android.support.v7.preference.*
|
||||
import android.view.View
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.extension.onClick
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.Utils
|
||||
import org.jetbrains.anko.act
|
||||
import org.jetbrains.anko.defaultSharedPreferences
|
||||
import org.jetbrains.anko.startActivity
|
||||
import org.jetbrains.anko.toast
|
||||
import libv2ray.Libv2ray
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
companion object {
|
||||
@@ -42,7 +32,6 @@ class SettingsActivity : BaseActivity() {
|
||||
// const val PREF_LICENSES = "pref_licenses"
|
||||
// const val PREF_FEEDBACK = "pref_feedback"
|
||||
// const val PREF_TG_GROUP = "pref_tg_group"
|
||||
const val PREF_VERSION = "pref_version"
|
||||
// const val PREF_AUTO_RESTART = "pref_auto_restart"
|
||||
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
|
||||
}
|
||||
@@ -56,18 +45,18 @@ class SettingsActivity : BaseActivity() {
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragment(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||
val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||
val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||
val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||
val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||
val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference }
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
private val perAppProxy by lazy { findPreference(PREF_PER_APP_PROXY) as CheckBoxPreference }
|
||||
private val sppedEnabled by lazy { findPreference(PREF_SPEED_ENABLED) as CheckBoxPreference }
|
||||
private val sniffingEnabled by lazy { findPreference(PREF_SNIFFING_ENABLED) as CheckBoxPreference }
|
||||
private val proxySharing by lazy { findPreference(PREF_PROXY_SHARING) as CheckBoxPreference }
|
||||
private val domainStrategy by lazy { findPreference(PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
|
||||
private val routingMode by lazy { findPreference(PREF_ROUTING_MODE) as ListPreference }
|
||||
|
||||
val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||
val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||
val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||
val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
|
||||
private val forwardIpv6 by lazy { findPreference(PREF_FORWARD_IPV6) as CheckBoxPreference }
|
||||
private val enableLocalDns by lazy { findPreference(PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
|
||||
private val domesticDns by lazy { findPreference(PREF_DOMESTIC_DNS) as EditTextPreference }
|
||||
private val remoteDns by lazy { findPreference(PREF_REMOTE_DNS) as EditTextPreference }
|
||||
|
||||
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
|
||||
|
||||
@@ -75,32 +64,31 @@ class SettingsActivity : BaseActivity() {
|
||||
// val socksPort by lazy { findPreference(PREF_SOCKS_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 licenses: Preference by lazy { findPreference(PREF_LICENSES) }
|
||||
// val feedback: Preference by lazy { findPreference(PREF_FEEDBACK) }
|
||||
// val tgGroup: Preference by lazy { findPreference(PREF_TG_GROUP) }
|
||||
val version: Preference by lazy { findPreference(PREF_VERSION) }
|
||||
|
||||
private val mode by lazy { findPreference(AppConfig.PREF_MODE) as ListPreference }
|
||||
|
||||
private fun restartProxy() {
|
||||
Utils.stopVService(activity)
|
||||
Utils.startVService(activity)
|
||||
Utils.stopVService(requireContext())
|
||||
Utils.startVService(requireContext())
|
||||
}
|
||||
|
||||
private fun isRunning(): Boolean {
|
||||
return Utils.isServiceRun(activity, "com.v2ray.ang.service.V2RayVpnService")
|
||||
return false //TODO no point of adding logic now since Settings will be changed soon
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
override fun onCreatePreferences(bundle: Bundle?, s: String?) {
|
||||
addPreferencesFromResource(R.xml.pref_settings)
|
||||
var app = activity.application as AngApplication
|
||||
|
||||
perAppProxy.setOnPreferenceClickListener {
|
||||
if (isRunning()) {
|
||||
Utils.stopVService(activity)
|
||||
Utils.stopVService(requireContext())
|
||||
}
|
||||
startActivity<PerAppProxyActivity>()
|
||||
startActivity(Intent(activity, PerAppProxyActivity::class.java))
|
||||
perAppProxy.isChecked = true
|
||||
true
|
||||
}
|
||||
@@ -117,7 +105,7 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
proxySharing.setOnPreferenceClickListener {
|
||||
if (proxySharing.isChecked)
|
||||
toast(R.string.toast_warning_pref_proxysharing)
|
||||
activity?.toast(R.string.toast_warning_pref_proxysharing)
|
||||
if (isRunning())
|
||||
restartProxy()
|
||||
true
|
||||
@@ -134,10 +122,11 @@ class SettingsActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
routingCustom.onClick {
|
||||
routingCustom.setOnPreferenceClickListener {
|
||||
if (isRunning())
|
||||
Utils.stopVService(activity)
|
||||
startActivity<RoutingSettingsActivity>()
|
||||
Utils.stopVService(requireContext())
|
||||
startActivity(Intent(activity, RoutingSettingsActivity::class.java))
|
||||
false
|
||||
}
|
||||
|
||||
forwardIpv6.setOnPreferenceClickListener {
|
||||
@@ -153,7 +142,7 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
|
||||
domesticDns.setOnPreferenceChangeListener { preference, any ->
|
||||
domesticDns.setOnPreferenceChangeListener { _, any ->
|
||||
// domesticDns.summary = any as String
|
||||
val nval = any as String
|
||||
domesticDns.summary = if (nval == "") AppConfig.DNS_DIRECT else nval
|
||||
@@ -162,7 +151,7 @@ class SettingsActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
remoteDns.setOnPreferenceChangeListener { preference, any ->
|
||||
remoteDns.setOnPreferenceChangeListener { _, any ->
|
||||
// remoteDns.summary = any as String
|
||||
val nval = any as String
|
||||
remoteDns.summary = if (nval == "") AppConfig.DNS_AGENT else nval
|
||||
@@ -171,6 +160,12 @@ class SettingsActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
mode.setOnPreferenceChangeListener { _, newValue ->
|
||||
updatePerAppProxy(newValue.toString())
|
||||
true
|
||||
}
|
||||
mode.dialogLayoutResource = R.layout.preference_with_help_link
|
||||
|
||||
// donate.onClick {
|
||||
// startActivity<InappBuyActivity>()
|
||||
// }
|
||||
@@ -206,14 +201,12 @@ class SettingsActivity : BaseActivity() {
|
||||
// httpPort.summary = any as String
|
||||
// true
|
||||
// }
|
||||
|
||||
version.summary = "${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
perAppProxy.isChecked = defaultSharedPreferences.getBoolean(PREF_PER_APP_PROXY, false)
|
||||
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
updatePerAppProxy(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
|
||||
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "")
|
||||
|
||||
@@ -227,24 +220,21 @@ class SettingsActivity : BaseActivity() {
|
||||
|
||||
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
|
||||
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
|
||||
|
||||
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
defaultSharedPreferences.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
// PREF_AUTO_RESTART ->
|
||||
// act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
|
||||
|
||||
PREF_PER_APP_PROXY ->
|
||||
act.defaultDPreference.setPrefBoolean(key, sharedPreferences.getBoolean(key, false))
|
||||
private fun updatePerAppProxy(mode: String?) {
|
||||
if (mode == "VPN") {
|
||||
perAppProxy.isEnabled = true
|
||||
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.getBoolean(PREF_PER_APP_PROXY, false)
|
||||
} else {
|
||||
perAppProxy.isEnabled = false
|
||||
perAppProxy.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
fun onModeHelpClicked(view: View) {
|
||||
Utils.openUri(this, AppConfig.v2rayNGWikiMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.android.synthetic.main.activity_sub_edit.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
|
||||
class SubEditActivity : BaseActivity() {
|
||||
|
||||
@@ -95,17 +95,16 @@ class SubEditActivity : BaseActivity() {
|
||||
*/
|
||||
fun deleteServer(): Boolean {
|
||||
if (edit_index >= 0) {
|
||||
alert(R.string.del_config_comfirm) {
|
||||
positiveButton(android.R.string.ok) {
|
||||
if (AngConfigManager.removeSubItem(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (AngConfigManager.removeSubItem(edit_index) == 0) {
|
||||
toast(R.string.toast_success)
|
||||
finish()
|
||||
} else {
|
||||
toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
show()
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
}
|
||||
return true
|
||||
@@ -135,4 +134,4 @@ class SubEditActivity : BaseActivity() {
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.support.v7.widget.LinearLayoutManager
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import com.v2ray.ang.R
|
||||
import kotlinx.android.synthetic.main.activity_sub_setting.*
|
||||
import android.os.Bundle
|
||||
import org.jetbrains.anko.startActivity
|
||||
|
||||
class SubSettingActivity : BaseActivity() {
|
||||
|
||||
@@ -40,7 +40,9 @@ class SubSettingActivity : BaseActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.add_config -> {
|
||||
startActivity<SubEditActivity>("position" to -1)
|
||||
startActivity(Intent(this, SubEditActivity::class.java)
|
||||
.putExtra("position", -1)
|
||||
)
|
||||
adapter.updateConfigList()
|
||||
true
|
||||
}
|
||||
@@ -48,4 +50,4 @@ class SubSettingActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.support.v7.widget.RecyclerView
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import kotlinx.android.synthetic.main.item_recycler_sub_setting.view.*
|
||||
import org.jetbrains.anko.*
|
||||
|
||||
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
|
||||
|
||||
@@ -28,17 +29,19 @@ class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView
|
||||
|
||||
holder.name.text = remarks
|
||||
holder.url.text = url
|
||||
holder.itemView.backgroundColor = Color.TRANSPARENT
|
||||
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
mActivity.startActivity<SubEditActivity>("position" to position)
|
||||
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
|
||||
.putExtra("position", position)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.EncodeHintType
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import android.app.ActivityManager
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
@@ -25,18 +24,17 @@ import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.responseLength
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.v2RayApplication
|
||||
import com.v2ray.ang.service.V2RayVpnService
|
||||
import com.v2ray.ang.service.V2RayServiceManager
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import kotlinx.coroutines.isActive
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.jetbrains.anko.toast
|
||||
import java.io.IOException
|
||||
import java.net.*
|
||||
import libv2ray.Libv2ray
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
|
||||
object Utils {
|
||||
|
||||
val tcpTestingSockets = ArrayList<Socket?>()
|
||||
@@ -273,32 +271,12 @@ object Utils {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断服务是否后台运行
|
||||
|
||||
* @param context
|
||||
* * Context
|
||||
* *
|
||||
* @param className
|
||||
* * 判断的服务名字
|
||||
* *
|
||||
* @return true 在运行 false 不在运行
|
||||
*/
|
||||
fun isServiceRun(context: Context, className: String): Boolean {
|
||||
var isRun = false
|
||||
val activityManager = context
|
||||
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
val serviceList = activityManager
|
||||
.getRunningServices(999)
|
||||
val size = serviceList.size
|
||||
for (i in 0..size - 1) {
|
||||
if (serviceList[i].service.className == className) {
|
||||
isRun = true
|
||||
break
|
||||
}
|
||||
fun startVServiceFromToggle(context: Context): Boolean {
|
||||
val result = startVService(context)
|
||||
if (!result) {
|
||||
context.toast(R.string.app_tile_first_use)
|
||||
}
|
||||
return isRun
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +299,7 @@ object Utils {
|
||||
return false
|
||||
}
|
||||
}
|
||||
V2RayVpnService.startV2Ray(context)
|
||||
V2RayServiceManager.startV2Ray(context, context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN"))
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
||||
@@ -3,9 +3,7 @@ package com.v2ray.ang.util
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.*
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
||||
@@ -14,7 +12,6 @@ import com.v2ray.ang.ui.SettingsActivity
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import kotlin.collections.ArrayList
|
||||
@@ -64,11 +61,16 @@ object V2rayConfigUtil {
|
||||
if (TextUtils.isEmpty(jsonConfig)) {
|
||||
return null
|
||||
}
|
||||
val v2rayConfig = Gson().fromJson(jsonConfig, V2rayConfig::class.java) ?: return null
|
||||
for (outbound in v2rayConfig.outbounds) {
|
||||
if (outbound.protocol.equals(EConfigType.VMESS.name.toLowerCase()) ||
|
||||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) ||
|
||||
outbound.protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
val v2rayConfig: V2rayConfig? = try {
|
||||
Gson().fromJson(jsonConfig, V2rayConfig::class.java)
|
||||
} catch (e: JsonSyntaxException) {
|
||||
e.printStackTrace()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,23 @@
|
||||
app:headerLayout="@layout/nav_header"
|
||||
app:itemIconTint="@color/colorPrimary_dark"
|
||||
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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
@@ -16,25 +16,27 @@
|
||||
|
||||
</group>
|
||||
|
||||
<item android:title="@string/title_about">
|
||||
<menu>
|
||||
<item
|
||||
<group android:id="@+id/group_id2">
|
||||
<item
|
||||
android:id="@+id/promotion"
|
||||
android:icon="@drawable/ic_whatshot_white_24dp"
|
||||
android:title="@string/title_pref_promotion" />
|
||||
<item
|
||||
<item
|
||||
android:id="@+id/donate"
|
||||
android:icon="@drawable/ic_attach_money_white_24dp"
|
||||
android:title="@string/title_pref_donate" />
|
||||
<item
|
||||
<item
|
||||
android:id="@+id/logcat"
|
||||
android:icon="@drawable/ic_logcat_white_24dp"
|
||||
android:title="@string/title_logcat" />
|
||||
<item
|
||||
<item
|
||||
android:id="@+id/feedback"
|
||||
android:icon="@drawable/ic_feedback_white_24dp"
|
||||
android:title="@string/title_pref_feedback" />
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
<!-- place holder for version text at the bottom -->
|
||||
<item
|
||||
android:id="@+id/placeholder"
|
||||
android:enabled="false"
|
||||
android:title="" />
|
||||
</group>
|
||||
</menu>
|
||||
|
||||
@@ -17,17 +17,6 @@
|
||||
<copyright>Copyright(C) 2008-2011 The Android Open Source Project</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Apache Commons Validator</name>
|
||||
<url>http://commons.apache.org/proper/commons-collections/</url>
|
||||
<copyright>Copyright(C) 2001-2014 The Apache Software Foundation</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>anko</name>
|
||||
<url>https://github.com/Kotlin/anko</url>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>Google Gson</name>
|
||||
<url>https://github.com/google/gson</url>
|
||||
@@ -52,33 +41,9 @@
|
||||
<copyright>Copyright 2015 Square, Inc.</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>ReactiveNetwork</name>
|
||||
<url>https://github.com/pwittchen/ReactiveNetwork</url>
|
||||
<copyright>Copyright 2016 Piotr Wittchen</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>RecyclerItemDecoration</name>
|
||||
<url>https://github.com/dinuscxj/RecyclerItemDecoration</url>
|
||||
<copyright>Copyright 2015-2019 dinus</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>RxKotlin</name>
|
||||
<url>https://github.com/ReactiveX/RxKotlin</url>
|
||||
<copyright>Copyright 2012 Netflix, Inc.</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>RxPermissions</name>
|
||||
<url>https://github.com/tbruyelle/RxPermissions</url>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
<notice>
|
||||
<name>RecyclerItemDecoration</name>
|
||||
<url>https://github.com/dinuscxj/RecyclerItemDecoration</url>
|
||||
<copyright>Copyright 2015-2019 dinus</copyright>
|
||||
<license>Apache Software License 2.0</license>
|
||||
</notice>
|
||||
</notices>
|
||||
</notices>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- https://stackoverflow.com/a/52960668/5495739 -->
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<bool name="config_materialPreferenceIconSpaceReserved" tools:ignore="MissingDefaultResource,PrivateResource">false</bool>
|
||||
<dimen name="preference_category_padding_start" tools:ignore="MissingDefaultResource,PrivateResource">0dp</dimen>
|
||||
</resources>
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">v2rayNG</string>
|
||||
<string name="app_widget_name">开关</string>
|
||||
<string name="app_tile_name">开关</string>
|
||||
<string name="app_tile_first_use">初次使用此功能请先用APP激活VPN</string>
|
||||
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">设置</string>
|
||||
<string name="title_about">关于</string>
|
||||
<string name="title_advanced">进阶设置</string>
|
||||
|
||||
<string name="title_pref_per_app_proxy">分应用代理</string>
|
||||
@@ -123,7 +122,8 @@
|
||||
<string name="title_pref_promotion">推广</string>
|
||||
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
|
||||
|
||||
<string name="title_pref_version">版本</string>
|
||||
<string name="title_mode">模式</string>
|
||||
<string name="title_mode_help">点此查看更多帮助</string>
|
||||
|
||||
<string name="donate_error_setup">初始化错误:</string>
|
||||
<string name="donate_error_inventory">无法查询到项目</string>
|
||||
@@ -187,4 +187,9 @@
|
||||
<string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string>
|
||||
<string name="toast_malformed_josn">配置格式错误</string>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>仅代理</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">v2rayNG</string>
|
||||
<string name="app_widget_name">切換</string>
|
||||
<string name="app_tile_name">切換</string>
|
||||
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式來啟用 VPN</string>
|
||||
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">設定</string>
|
||||
<string name="title_about">關於</string>
|
||||
<string name="title_advanced">進階設定</string>
|
||||
|
||||
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
|
||||
@@ -125,7 +124,8 @@
|
||||
<string name="title_pref_promotion">推廣</string>
|
||||
<string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string>
|
||||
|
||||
<string name="title_pref_version">版本</string>
|
||||
<string name="title_mode">模式</string>
|
||||
<string name="title_mode_help">點此查看更多幫助</string>
|
||||
|
||||
<string name="donate_error_setup">錯誤設定:</string>
|
||||
<string name="donate_error_inventory">Error querying inventory</string>
|
||||
@@ -188,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_short">代理共享已啟用,請確保處於受信網路</string>
|
||||
<string name="toast_malformed_josn">配置格式錯誤</string>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>僅代理</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -67,6 +67,11 @@
|
||||
<item>IPOnDemand</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="mode_value" translatable="false">
|
||||
<item>VPN</item>
|
||||
<item>Proxy only</item>
|
||||
</string-array>
|
||||
|
||||
<!-- minimum list https://serverfault.com/a/304791 -->
|
||||
<string-array name="bypass_private_ip_address" translatable="false">
|
||||
<item>0.0.0.0/5</item>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">v2rayNG</string>
|
||||
<string name="app_widget_name">Switch</string>
|
||||
<string name="app_tile_name">Switch</string>
|
||||
<string name="app_tile_first_use">First use of this feature, please use the app to activate VPN</string>
|
||||
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string>
|
||||
<string name="navigation_drawer_open">Open navigation drawer</string>
|
||||
<string name="navigation_drawer_close">Close navigation drawer</string>
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">Settings</string>
|
||||
<string name="title_about">About</string>
|
||||
<string name="title_advanced">Advanced Settings</string>
|
||||
|
||||
<string name="title_pref_per_app_proxy">Per-app proxy</string>
|
||||
@@ -125,7 +124,8 @@
|
||||
<string name="title_pref_promotion">Promotion</string>
|
||||
<string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string>
|
||||
|
||||
<string name="title_pref_version">Version</string>
|
||||
<string name="title_mode">Mode</string>
|
||||
<string name="title_mode_help">Click me for more help</string>
|
||||
|
||||
<string name="donate_error_setup">Error Setup:</string>
|
||||
<string name="donate_error_inventory">Error querying inventory</string>
|
||||
@@ -189,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_malformed_josn">Config malformed</string>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>Proxy only</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -85,29 +85,13 @@
|
||||
android:key="pref_http_port"
|
||||
android:summary="10809"
|
||||
android:title="@string/title_pref_http_port" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="VPN"
|
||||
android:entries="@array/mode_entries"
|
||||
android:entryValues="@array/mode_value"
|
||||
android:key="pref_mode"
|
||||
android:summary="%s"
|
||||
android:title="@string/title_mode" />
|
||||
</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>
|
||||
|
||||
@@ -7,7 +7,7 @@ buildscript {
|
||||
maven { url 'https://maven.google.com' }
|
||||
}
|
||||
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"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
@@ -19,7 +19,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
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.google.code.gson:gson:2.7'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
}
|
||||
|
||||
@@ -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
|
||||
# org.gradle.parallel=true
|
||||
#Fri Jun 02 14:08:42 CST 2017
|
||||
ankoVersion=0.10.8
|
||||
kotlinVersion=1.3.40
|
||||
kotlinVersion=1.4.10
|
||||
supportLibVersion=28.0.0
|
||||
buildToolsVer=29.0.3
|
||||
compileSdkVer=29
|
||||
buildToolsVer=30.0.2
|
||||
compileSdkVer=30
|
||||
kotlin.incremental=true
|
||||
targetSdkVer=29
|
||||
targetSdkVer=30
|
||||
|
||||
Reference in New Issue
Block a user