Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df3f1ca3ef | ||
|
|
c54d8fa43a | ||
|
|
5bbbdcf6f2 | ||
|
|
4abf20fa32 | ||
|
|
723727feb9 | ||
|
|
2efd4b741c | ||
|
|
83aab0f880 | ||
|
|
66ea17877e | ||
|
|
26bc985368 | ||
|
|
5bbf40c784 | ||
|
|
6d5c23245c | ||
|
|
b148290211 | ||
|
|
c473f9bb13 | ||
|
|
c7c3d27f36 | ||
|
|
bebc6fea13 | ||
|
|
9271857b1e | ||
|
|
7ea78c1840 | ||
|
|
ac668788b3 | ||
|
|
adfcf0a5d9 | ||
|
|
15b5595797 | ||
|
|
5a18296cb2 | ||
|
|
1256edbaf5 | ||
|
|
870950e807 | ||
|
|
e798cb3f42 | ||
|
|
b970f0bcff | ||
|
|
93b9a56428 | ||
|
|
eb8bd13266 | ||
|
|
d850c88c63 | ||
|
|
6bee795c0d | ||
|
|
d7d7e029e0 | ||
|
|
8efdab43d7 | ||
|
|
5e0235cf70 | ||
|
|
9f668b3da7 | ||
|
|
b2437279dc | ||
|
|
180b5efd93 | ||
|
|
dc31380cc2 | ||
|
|
1361e0dacf | ||
|
|
a45eb66bd1 | ||
|
|
508102cebe | ||
|
|
1d86dbb9f3 | ||
|
|
20ca554be2 | ||
|
|
25ba455656 | ||
|
|
17e7c62d53 | ||
|
|
6f0f2fdeda | ||
|
|
3c9c9b5a4c | ||
|
|
e13024d6bb | ||
|
|
9d58edd31f | ||
|
|
af7dfc3a43 | ||
|
|
ca554e6ac4 | ||
|
|
ec391d8689 | ||
|
|
6afd4d0549 | ||
|
|
957cf85362 | ||
|
|
881152e10a | ||
|
|
6e47ebb27a | ||
|
|
a731e6c360 | ||
|
|
d1466ba4b2 | ||
|
|
617f28f399 | ||
|
|
c0ec0d9404 | ||
|
|
3419dc8837 | ||
|
|
786aaf823a | ||
|
|
1144183da4 | ||
|
|
5bf2fda990 | ||
|
|
28639cc388 | ||
|
|
ca254b2aa1 | ||
|
|
9721879713 | ||
|
|
623c1807c5 | ||
|
|
739fa88ba7 | ||
|
|
85d6f00f8c | ||
|
|
8986710453 | ||
|
|
f54faacbf6 | ||
|
|
993ee0b8d2 | ||
|
|
903352ec9c | ||
|
|
ad56106c08 | ||
|
|
91b8284afd | ||
|
|
ef9e0cc0d2 | ||
|
|
6577c46a31 | ||
|
|
c6dab001b2 | ||
|
|
aea8369b8a | ||
|
|
92d2cb35c4 | ||
|
|
6ce3d540e8 | ||
|
|
c105d84b35 | ||
|
|
8b149fb52f | ||
|
|
3ea04c076c | ||
|
|
98475460bf | ||
|
|
68ee61a753 | ||
|
|
90ba9ef2b7 | ||
|
|
0ec114322e | ||
|
|
76f52e7aa7 | ||
|
|
caa25ce424 | ||
|
|
e33d7e9bcf | ||
|
|
e120fce0b7 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,10 +2,8 @@ V2rayNG/app/src/main/res/layout/activity_inapp_buy.xml
|
||||
V2rayNG/app/src/main/assets/geoip.dat
|
||||
V2rayNG/app/src/main/assets/geosite.dat
|
||||
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
|
||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
||||
*.dat
|
||||
*.jks
|
||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
||||
V2rayNG/gradle/wrapper/gradle-wrapper.properties
|
||||
V2rayNG/app/release/output.json
|
||||
V2rayNG/app/release/output.json
|
||||
.idea/
|
||||
.gradle/
|
||||
@@ -5,14 +5,15 @@ import (
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
IsRunning bool
|
||||
PackageName string
|
||||
IsRunning bool
|
||||
PackageName string
|
||||
PackageCodePath string
|
||||
|
||||
Vpoint v2core.Server
|
||||
}
|
||||
|
||||
func CheckVersion() int {
|
||||
return 20
|
||||
return 21
|
||||
}
|
||||
|
||||
func (v *Status) GetDataDir() string {
|
||||
@@ -20,7 +21,7 @@ func (v *Status) GetDataDir() string {
|
||||
}
|
||||
|
||||
func (v *Status) GetApp(name string) string {
|
||||
return v.PackageName + name
|
||||
return v.PackageCodePath + name
|
||||
}
|
||||
|
||||
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
|
||||
|
||||
@@ -44,6 +44,7 @@ type V2RayPoint struct {
|
||||
closeChan chan struct{}
|
||||
|
||||
PackageName string
|
||||
PackageCodePath string
|
||||
DomainName string
|
||||
ConfigureFileContent string
|
||||
EnableLocalDNS bool
|
||||
@@ -67,6 +68,7 @@ func (v *V2RayPoint) RunLoop() (err error) {
|
||||
defer v.v2rayOP.Unlock()
|
||||
//Construct Context
|
||||
v.status.PackageName = v.PackageName
|
||||
v.status.PackageCodePath = v.PackageCodePath
|
||||
|
||||
if !v.status.IsRunning {
|
||||
v.closeChan = make(chan struct{})
|
||||
@@ -116,7 +118,7 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
|
||||
if v.statsManager == nil {
|
||||
return 0
|
||||
}
|
||||
counter := v.statsManager.GetCounter(fmt.Sprintf("inbound>>>%s>>>traffic>>>%s", tag, direct))
|
||||
counter := v.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
|
||||
if counter == nil {
|
||||
return 0
|
||||
}
|
||||
@@ -233,7 +235,7 @@ func (v V2RayPoint) runTun2socks() error {
|
||||
|
||||
v.escorter.EscortingUp()
|
||||
go v.escorter.EscortRun(
|
||||
v.status.GetApp("tun2socks"),
|
||||
v.status.GetApp("libtun2socks.so"),
|
||||
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
|
||||
v.SupportSet.SendFd)
|
||||
|
||||
|
||||
20
README.md
20
README.md
@@ -1,5 +1,25 @@
|
||||
# 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
|
||||
|
||||
See our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
|
||||
### Development guide
|
||||
|
||||
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
|
||||
The aar can be compiled from the Golang project under AndroidLibV2rayLite folder. For a quick start, read guide for [Go Mobile](https://github.com/golang/go/wiki/Mobile)
|
||||
and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)
|
||||
|
||||
v2rayNG can run on Android Emulators, with minimum Android 5.0
|
||||
|
||||
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||
buildToolsVersion buildToolsVer
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility = "8"
|
||||
@@ -20,34 +20,17 @@ android {
|
||||
versionName "1.0.2"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file("../key.jks")
|
||||
keyAlias 'ang'
|
||||
keyPassword '123456'
|
||||
storePassword '123456'
|
||||
}
|
||||
debug {
|
||||
storeFile file("../key.jks")
|
||||
keyAlias 'ang'
|
||||
keyPassword '123456'
|
||||
storePassword '123456'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
signingConfig signingConfigs.release
|
||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
minifyEnabled false
|
||||
zipAlignEnabled false
|
||||
shrinkResources false
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +62,14 @@ 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.3.9"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||
|
||||
// Android support library
|
||||
implementation "com.android.support:support-v4:$supportLibVersion"
|
||||
implementation "com.android.support:appcompat-v7:$supportLibVersion"
|
||||
@@ -91,12 +77,15 @@ dependencies {
|
||||
implementation "com.android.support:cardview-v7:$supportLibVersion"
|
||||
implementation "com.android.support:preference-v7:$supportLibVersion"
|
||||
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
|
||||
implementation "com.android.support:multidex:1.0.3"
|
||||
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
|
||||
|
||||
// DSL
|
||||
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
|
||||
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
|
||||
implementation 'com.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'
|
||||
@@ -106,7 +95,6 @@ dependencies {
|
||||
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')
|
||||
@@ -126,4 +114,4 @@ repositories {
|
||||
flatDir {
|
||||
dirs 'libs'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -2,6 +2,16 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.v2ray.ang">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
android:smallScreens="true"
|
||||
android:normalScreens="true"
|
||||
android:largeScreens="true"
|
||||
android:xlargeScreens="true"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
||||
|
||||
<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" />
|
||||
@@ -66,7 +76,11 @@
|
||||
|
||||
<activity android:name=".ui.SubEditActivity" />
|
||||
<activity android:name=".ui.ScScannerActivity" />
|
||||
<activity android:name=".ui.ScSwitchActivity" />
|
||||
<activity
|
||||
android:name=".ui.ScSwitchActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:process=":RunSoLibV2RayDaemon"
|
||||
android:theme="@style/AppTheme.NoActionBar.Translucent" />
|
||||
|
||||
<service
|
||||
android:name=".service.V2RayVpnService"
|
||||
@@ -83,22 +97,30 @@
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<!--<receiver android:name=".receiver.WidgetProvider">-->
|
||||
<!--<meta-data-->
|
||||
<!--android:name="android.appwidget.provider"-->
|
||||
<!--android:resource="@xml/app_widget_provider" />-->
|
||||
<service android:name=".service.V2RayProxyOnlyService"
|
||||
android:exported="false"
|
||||
android:label="@string/app_name"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
</service>
|
||||
|
||||
<!--<intent-filter>-->
|
||||
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
|
||||
<!--<action android:name="com.v2ray.ang.action.widget.click" />-->
|
||||
<!--</intent-filter>-->
|
||||
<!--</receiver>-->
|
||||
<receiver android:name=".receiver.WidgetProvider"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/app_widget_provider" />
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="com.v2ray.ang.action.widget.click" />
|
||||
<action android:name="com.v2ray.ang.action.activity" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
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>
|
||||
@@ -113,7 +135,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>
|
||||
@@ -122,4 +145,4 @@
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -1,196 +1,241 @@
|
||||
com.android.chrome
|
||||
com.google.android.googlequicksearchbox
|
||||
com.google.android.apps.photos
|
||||
com.google.android.youtube
|
||||
com.google.android.gm
|
||||
com.google.android.apps.plus
|
||||
com.android.vending
|
||||
com.google.android.inputmethod.latin
|
||||
com.google.android.apps.paidtasks
|
||||
com.google.android.keep
|
||||
com.google.android.gms.setup
|
||||
com. google.android. apps.magazines
|
||||
com.google.android.videos
|
||||
com. google.android.gms
|
||||
com.google.android.apps.books
|
||||
com.google.android.music
|
||||
com.google.android.play.games
|
||||
com.google.android.gsf
|
||||
com.google.android.gsf.login
|
||||
com.app.pornhub
|
||||
com.spotify.music
|
||||
org.thunderdog.challegram
|
||||
com.tumblr
|
||||
com.twitter.android
|
||||
com.xda.labs
|
||||
com.kapp.youtube.final
|
||||
com.google.android.ims
|
||||
com.wire
|
||||
mark.via.gp
|
||||
com.downloader.video.tumblr
|
||||
com.sololearn
|
||||
com.cygames.shadowverse
|
||||
com.felixfilip.scpae
|
||||
amanita_design.samorost3.gp
|
||||
com.devolver.reigns2
|
||||
com.utopia.pxview
|
||||
ch.protonmail.android
|
||||
com.perol.asdpl.pixivez
|
||||
com.pinterest
|
||||
com.paypal.android.p2pmobile
|
||||
com.arthurivanets.owly
|
||||
com.rubenmayayo.reddit
|
||||
com.rayark.cytus2
|
||||
com.rayark.pluto
|
||||
com.rayark.implosion
|
||||
com.fireproofstudios.theroom4
|
||||
com.netflix.mediaclient
|
||||
com.instagram.android
|
||||
com.google.android.apps.hangoutsdialer
|
||||
com.google.android.talk
|
||||
com.google.android.apps.plus
|
||||
com.google.android.apps.pdfviewer
|
||||
com.google.android.apps.magazines
|
||||
com.google.android.apps.nbu.files
|
||||
com.evernote
|
||||
net.tsapps.appsales
|
||||
com.google.android.apps.translate
|
||||
com.google.ar.lens
|
||||
com.google.android.apps.adm
|
||||
com.google.android.apps.googleassistant
|
||||
tw.com.gamer.android.activecenter
|
||||
org.telegram.plus
|
||||
com.brave.browser
|
||||
com.breel.wallpapers18
|
||||
com.teslacoilsw.launcher
|
||||
com.lastpass.lpandroid
|
||||
org.kustom.widget
|
||||
com.fooview.android.fooview
|
||||
com.google.android.apps.docs
|
||||
com.google.android.apps.maps
|
||||
com.facebook.services
|
||||
com.facebook.system
|
||||
com.facebook.katana
|
||||
com.nianticlabs.ingress.prime.qa
|
||||
com.vanced.android.youtube
|
||||
com.nianticproject.ingress
|
||||
com.quoord.tapatalkpro.activity
|
||||
org.mozilla.firefox
|
||||
com.reddit.frontpage
|
||||
com.google.android.apps.fitness
|
||||
android
|
||||
au.com.shiftyjelly.pocketcasts
|
||||
com.google.android.gms
|
||||
com.android.providers.telephony
|
||||
com.resilio.sync
|
||||
com.google.android.apps.googlevoice
|
||||
com.discord
|
||||
com.cradle.iitc_mobile
|
||||
bbc.mobile.news.ww
|
||||
be.mygod.vpnhotspot
|
||||
ch.protonmail.android
|
||||
co.wanqu.android
|
||||
com.alphainventor.filemanager
|
||||
com.amazon.kindle
|
||||
com.amazon.mshop.android.shopping
|
||||
com.android.chrome
|
||||
com.android.providers.downloads
|
||||
com.android.providers.downloads.ui
|
||||
com.android.providers.telephony
|
||||
com.android.settings
|
||||
com.android.vending
|
||||
com.android6park.m6park
|
||||
com.apkpure.aegon
|
||||
com.apkupdater
|
||||
com.app.pornhub
|
||||
com.arthurivanets.owly
|
||||
com.asahi.tida.tablet
|
||||
com.authy.authy
|
||||
com.avmovie
|
||||
com.ballistiq.artstation
|
||||
com.binance.dev
|
||||
com.bitly.app
|
||||
com.brave.browser
|
||||
com.brave.browser_beta
|
||||
com.breel.wallpapers18
|
||||
com.bvanced.android.youtube
|
||||
com.chrome.beta
|
||||
com.chrome.canary
|
||||
com.chrome.dev
|
||||
com.cl.newt66y
|
||||
com.cradle.iitc_mobile
|
||||
com.cygames.shadowverse
|
||||
com.devhd.feedly
|
||||
com.devolver.reigns2
|
||||
com.discord
|
||||
com.downloader.video.tumblr
|
||||
com.driverbrowser
|
||||
com.dropbox.android
|
||||
com.duolingo
|
||||
com.duckduckgo.mobile.android
|
||||
com.dv.adm
|
||||
com.estrongs.android.pop
|
||||
com.estrongs.android.pop.pro
|
||||
com.evernote
|
||||
com.facebook.katana
|
||||
com.facebook.lite
|
||||
com.facebook.mlite
|
||||
com.facebook.orca
|
||||
com.facebook.services
|
||||
com.facebook.system
|
||||
com.fastaccess.github
|
||||
com.felixfilip.scpae
|
||||
com.fireproofstudios.theroom4
|
||||
com.firstrowria.pushnotificationtester
|
||||
com.flyersoft.moonreaderp
|
||||
com.fooview.android.fooview
|
||||
com.fvd.eversync
|
||||
com.gameloft.android.anmp.glofta8hm
|
||||
com.gameloft.android.anmp.glofta9hm
|
||||
com.gianlu.aria2app
|
||||
com.github.yeriomin.yalpstore
|
||||
com.google.android.apps.adm
|
||||
com.google.android.apps.books
|
||||
com.google.android.apps.docs
|
||||
com.google.android.apps.docs.editors.sheets
|
||||
com.google.android.apps.fitness
|
||||
com.google.android.apps.googleassistant
|
||||
com.google.android.apps.googlevoice
|
||||
com.google.android.apps.hangoutsdialer
|
||||
com.google.android.apps.inbox
|
||||
com.google.android.apps.magazines
|
||||
com.google.android.apps.maps
|
||||
com.google.android.apps.nbu.files
|
||||
com.google.android.apps.paidtasks
|
||||
com.google.android.apps.pdfviewer
|
||||
com.google.android.apps.photos
|
||||
com.google.android.apps.plus
|
||||
com.google.android.apps.translate
|
||||
com.google.android.gm
|
||||
com.google.android.gms
|
||||
com.google.android.gms.setup
|
||||
com.google.android.googlequicksearchbox
|
||||
com.google.android.gsf
|
||||
com.google.android.gsf.login
|
||||
com.google.android.ims
|
||||
com.google.android.inputmethod.latin
|
||||
com.google.android.instantapps.supervisor
|
||||
com.google.android.keep
|
||||
com.google.android.music
|
||||
com.google.android.ogyoutube
|
||||
com.google.android.partnersetup
|
||||
com.google.android.play.games
|
||||
com.google.android.street
|
||||
com.google.android.syncadapters.calendar
|
||||
com.google.android.syncadapters.contacts
|
||||
com.google.android.talk
|
||||
com.google.android.tts
|
||||
com.google.android.videos
|
||||
com.google.android.youtube
|
||||
com.google.ar.lens
|
||||
com.hochan.coldsoup
|
||||
com.ifttt.ifttt
|
||||
com.imgur.mobile
|
||||
com.innologica.inoreader
|
||||
com.instagram.android
|
||||
com.instapaper.android
|
||||
com.jarvanh.vpntether
|
||||
com.kapp.youtube.final
|
||||
com.klinker.android.twitter_l
|
||||
com.lastpass.lpandroid
|
||||
com.linecorp.linelite
|
||||
com.lingodeer
|
||||
com.mediapods.tumbpods
|
||||
com.mgoogle.android.gms
|
||||
com.microsoft.emmx
|
||||
com.microsoft.office.powerpoint
|
||||
com.microsoft.skydrive
|
||||
com.mixplorer
|
||||
com.msd.consumerchinese
|
||||
com.msd.professionalchinese
|
||||
com.mss2011c.sharehelper
|
||||
com.netflix.mediaclient
|
||||
com.newin.nplayer.pro
|
||||
com.nianticlabs.ingress.prime.qa
|
||||
com.nianticproject.ingress
|
||||
com.ninefolders.hd3
|
||||
com.ninegag.android.app
|
||||
com.nintendo.zara
|
||||
com.nytimes.cn
|
||||
com.oasisfeng.island
|
||||
com.ocnt.liveapp.hw
|
||||
com.orekie.search
|
||||
com.patreon.android
|
||||
com.paypal.android.p2pmobile
|
||||
com.perol.asdpl.pixivez
|
||||
com.pinterest
|
||||
com.popularapp.periodcalendar
|
||||
com.popularapp.videodownloaderforinstagram
|
||||
com.pushbullet.android
|
||||
com.quoord.tapatalkpro.activity
|
||||
com.quora.android
|
||||
com.rayark.cytus2
|
||||
com.rayark.implosion
|
||||
com.rayark.pluto
|
||||
com.reddit.frontpage
|
||||
com.resilio.sync
|
||||
com.rhmsoft.edit
|
||||
com.rubenmayayo.reddit
|
||||
com.sec.android.app.sbrowser
|
||||
com.sec.android.app.sbrowser.beta
|
||||
com.shanga.walli
|
||||
com.simplehabit.simplehabitapp
|
||||
com.slack
|
||||
com.snaptube.premium
|
||||
com.sololearn
|
||||
com.sonelli.juicessh
|
||||
com.spotify.music
|
||||
com.tencent.huatuo
|
||||
com.termux
|
||||
com.teslacoilsw.launcher
|
||||
com.theinitium.news
|
||||
com.thomsonreuters.reuters
|
||||
com.thunkable.android.hritvik00.freenom
|
||||
com.topjohnwu.magisk
|
||||
com.tripadvisor.tripadvisor
|
||||
com.tumblr
|
||||
com.twitter.android
|
||||
com.u91porn
|
||||
com.u9porn
|
||||
com.ubisoft.dance.justdance2015companion
|
||||
com.utopia.pxview
|
||||
com.valvesoftware.android.steam.communimunity
|
||||
com.valvesoftware.android.steam.community
|
||||
com.vanced.android.youtube
|
||||
com.vimeo.android.videoapp
|
||||
com.vivaldi.browser
|
||||
com.vivaldi.browser.snapshot
|
||||
com.vkontakte.android
|
||||
com.whatsapp
|
||||
com.wire
|
||||
com.wuxiangai.refactor
|
||||
com.xda.labs
|
||||
com.xvideos.app
|
||||
com.yandex.browser
|
||||
com.yandex.browser.beta
|
||||
com.yandex.browser.alpha
|
||||
com.z28j.feel
|
||||
con.medium.reader
|
||||
de.apkgrabber
|
||||
de.robv.android.xposed.installer
|
||||
dk.tacit.android.foldersync.full
|
||||
es.rafalense.telegram.themes
|
||||
es.rafalense.themes
|
||||
flipboard.app
|
||||
fm.moon.app
|
||||
fr.gouv.etalab.mastodon
|
||||
github.tornaco.xposedmoduletest
|
||||
idm.internet.download.manager
|
||||
idm.internet.download.manager.plus
|
||||
io.github.javiewer
|
||||
io.github.skyhacker2.magnetsearch
|
||||
io.va.exposed
|
||||
it.mvilla.android.fenix2
|
||||
jp.bokete.app.android
|
||||
jp.naver.line.android
|
||||
jp.pxv.android
|
||||
luo.speedometergpspro
|
||||
mark.via.gp
|
||||
me.tshine.easymark
|
||||
net.teeha.android.url_shortener
|
||||
net.tsapps.appsales
|
||||
onion.fire
|
||||
org.fdroid.fdroid
|
||||
org.freedownloadmanager.fdm
|
||||
org.kustom.widget
|
||||
org.mozilla.fennec_aurora
|
||||
org.mozilla.fenix
|
||||
org.mozilla.fenix.nightly
|
||||
org.mozilla.firefox
|
||||
org.mozilla.firefox_beta
|
||||
org.mozilla.focus
|
||||
org.schabi.newpipe
|
||||
org.telegram.messenger
|
||||
org.telegram.multi
|
||||
org.telegram.plus
|
||||
org.thunderdog.challegram
|
||||
org.torproject.android
|
||||
org.torproject.torbrowser_alpha
|
||||
org.wikipedia
|
||||
org.xbmc.kodi
|
||||
pl.zdunex25.updater
|
||||
videodownloader.downloadvideo.downloader
|
||||
com.quora.android
|
||||
com.lingodeer
|
||||
org.wikipedia
|
||||
com.ninegag.android.app
|
||||
com.duolingo
|
||||
com.patreon.android
|
||||
com.valvesoftware.android.steam.communimunity
|
||||
co.wanqu.android
|
||||
jp.bokete.app.android
|
||||
com.vkontakte.android
|
||||
com.amazon.mshop.android.shopping
|
||||
com.ubisoft.dance.justdance2015companion
|
||||
com.gameloft.android.anmp.glofta8hm
|
||||
com.gameloft.android.anmp.glofta9hm
|
||||
com.binance.dev
|
||||
com.asahi.tida.tablet
|
||||
com.theinitium.news
|
||||
com.driverbrowser
|
||||
com.thomsonreuters.reuters
|
||||
com.nytimes.cn
|
||||
com.android.providers.downloads.ui
|
||||
com.avmovie
|
||||
bbc.mobile.news.ww
|
||||
org.mozilla.focus
|
||||
io.github.javiewer
|
||||
com.sonelli.juicessh
|
||||
con.medium.reader
|
||||
com.microsoft.skydrive
|
||||
com.valvesoftware.android.steam.community
|
||||
com.nintendo.zara
|
||||
org.torproject.torbrowser_alpha
|
||||
tv.twitch.android.app
|
||||
com.shanga.walli
|
||||
com.whatsapp
|
||||
com.wire
|
||||
com.simplehabit.simplehabitapp
|
||||
tw.com.gamer.android.activecenter
|
||||
videodownloader.downloadvideo.downloader
|
||||
uk.co.bbc.learningenglish
|
||||
com.ted.android
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
}
|
||||
},
|
||||
"system": {
|
||||
"statsInboundUplink": true,
|
||||
"statsInboundDownlink": true
|
||||
"statsOutboundUplink": true,
|
||||
"statsOutboundDownlink": true
|
||||
}
|
||||
},
|
||||
"inbounds": [{
|
||||
|
||||
@@ -43,6 +43,8 @@ public interface ItemTouchHelperAdapter {
|
||||
boolean onItemMove(int fromPosition, int toPosition);
|
||||
|
||||
|
||||
void onItemMoveCompleted();
|
||||
|
||||
/**
|
||||
* Called when an item has been dismissed by a swipe.<br/>
|
||||
* <br/>
|
||||
|
||||
@@ -112,6 +112,8 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
|
||||
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
super.clearView(recyclerView, viewHolder);
|
||||
|
||||
mAdapter.onItemMoveCompleted();
|
||||
|
||||
viewHolder.itemView.setAlpha(ALPHA_FULL);
|
||||
|
||||
if (viewHolder instanceof ItemTouchHelperViewHolder) {
|
||||
|
||||
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
Normal file
BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
Normal file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
Normal file
BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
Normal file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
Normal file
BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so
Normal file
Binary file not shown.
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
Normal file
BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so
Normal file
Binary file not shown.
@@ -1,12 +1,12 @@
|
||||
package com.v2ray.ang
|
||||
|
||||
import android.app.Application
|
||||
//import com.squareup.leakcanary.LeakCanary
|
||||
import android.support.multidex.MultiDexApplication
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.jetbrains.anko.defaultSharedPreferences
|
||||
|
||||
class AngApplication : Application() {
|
||||
class AngApplication : MultiDexApplication() {
|
||||
companion object {
|
||||
const val PREF_LAST_VERSION = "pref_last_version"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ object AppConfig {
|
||||
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid"
|
||||
const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name"
|
||||
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
|
||||
const val PREF_CURR_CONFIG_OUTBOUND_TAGS = "pref_v2ray_config_outbound_tags"
|
||||
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
|
||||
const val PREF_MODE = "pref_mode"
|
||||
|
||||
const val VMESS_PROTOCOL: String = "vmess://"
|
||||
const val SS_PROTOCOL: String = "ss://"
|
||||
const val SOCKS_PROTOCOL: String = "socks://"
|
||||
@@ -35,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"
|
||||
@@ -50,12 +54,4 @@ object AppConfig {
|
||||
const val MSG_STATE_STOP = 4
|
||||
const val MSG_STATE_STOP_SUCCESS = 41
|
||||
const val MSG_STATE_RESTART = 5
|
||||
|
||||
object EConfigType {
|
||||
val Vmess = 1
|
||||
val Custom = 2
|
||||
val Shadowsocks = 3
|
||||
val Socks = 4
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
12
V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/EConfigType.kt
Normal file
12
V2rayNG/app/src/main/kotlin/com/v2ray/ang/dto/EConfigType.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package com.v2ray.ang.dto
|
||||
|
||||
enum class EConfigType(val value: Int) {
|
||||
VMESS(1),
|
||||
CUSTOM(2),
|
||||
SHADOWSOCKS(3),
|
||||
SOCKS(4);
|
||||
|
||||
companion object {
|
||||
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
|
||||
}
|
||||
}
|
||||
@@ -64,22 +64,21 @@ data class V2rayConfig(
|
||||
|
||||
data class StreamSettingsBean(var network: String,
|
||||
var security: String,
|
||||
var tcpSettings: TcpsettingsBean?,
|
||||
var kcpsettings: KcpsettingsBean?,
|
||||
var wssettings: WssettingsBean?,
|
||||
var httpsettings: HttpsettingsBean?,
|
||||
var tlssettings: TlssettingsBean?,
|
||||
var quicsettings: QuicsettingBean?
|
||||
var tcpSettings: TcpSettingsBean?,
|
||||
var kcpSettings: KcpSettingsBean?,
|
||||
var wsSettings: WsSettingsBean?,
|
||||
var httpSettings: HttpSettingsBean?,
|
||||
var tlsSettings: TlsSettingsBean?,
|
||||
var quicSettings: QuicSettingBean?
|
||||
) {
|
||||
|
||||
data class TcpsettingsBean(var connectionReuse: Boolean = true,
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none",
|
||||
var request: Any? = null,
|
||||
var response: Any? = null)
|
||||
}
|
||||
|
||||
data class KcpsettingsBean(var mtu: Int = 1350,
|
||||
data class KcpSettingsBean(var mtu: Int = 1350,
|
||||
var tti: Int = 20,
|
||||
var uplinkCapacity: Int = 12,
|
||||
var downlinkCapacity: Int = 100,
|
||||
@@ -90,25 +89,43 @@ data class V2rayConfig(
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
|
||||
data class WssettingsBean(var connectionReuse: Boolean = true,
|
||||
var path: String = "",
|
||||
data class WsSettingsBean(var path: String = "",
|
||||
var headers: HeadersBean = HeadersBean()) {
|
||||
data class HeadersBean(var Host: String = "")
|
||||
}
|
||||
|
||||
data class HttpsettingsBean(var host: List<String> = ArrayList<String>(), var path: String = "")
|
||||
data class HttpSettingsBean(var host: List<String> = ArrayList(),
|
||||
var path: String = "")
|
||||
|
||||
data class TlssettingsBean(var allowInsecure: Boolean = true,
|
||||
data class TlsSettingsBean(var allowInsecure: Boolean = true,
|
||||
var serverName: String = "")
|
||||
|
||||
data class QuicsettingBean(var security: String = "none",
|
||||
var key: String = "",
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class QuicSettingBean(var security: String = "none",
|
||||
var key: String = "",
|
||||
var header: HeaderBean = HeaderBean()) {
|
||||
data class HeaderBean(var type: String = "none")
|
||||
}
|
||||
}
|
||||
|
||||
data class MuxBean(var enabled: Boolean)
|
||||
|
||||
fun getServerAddress(): String? {
|
||||
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
|
||||
return settings?.vnext?.get(0)?.address
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
return settings?.servers?.get(0)?.address
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getServerPort(): Int? {
|
||||
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
|
||||
return settings?.vnext?.get(0)?.port
|
||||
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
return settings?.servers?.get(0)?.port
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
//data class DnsBean(var servers: List<String>)
|
||||
|
||||
@@ -27,34 +27,37 @@ const val divisor = 1024F
|
||||
fun Long.toSpeedString() = toTrafficString() + "/s"
|
||||
|
||||
fun Long.toTrafficString(): String {
|
||||
if (this == 0L)
|
||||
return "\t\t\t0\t B"
|
||||
|
||||
if (this < threshold)
|
||||
return "$this B"
|
||||
return "${this.toFloat().toShortString()}\t B"
|
||||
|
||||
val kib = this / divisor
|
||||
if (kib < threshold)
|
||||
return "${kib.toShortString()} KB"
|
||||
return "${kib.toShortString()}\t KB"
|
||||
|
||||
val mib = kib / divisor
|
||||
if (mib < threshold)
|
||||
return "${mib.toShortString()} MB"
|
||||
return "${mib.toShortString()}\t MB"
|
||||
|
||||
val gib = mib / divisor
|
||||
if (gib < threshold)
|
||||
return "${gib.toShortString()} GB"
|
||||
return "${gib.toShortString()}\t GB"
|
||||
|
||||
val tib = gib / divisor
|
||||
if (tib < threshold)
|
||||
return "${tib.toShortString()} TB"
|
||||
return "${tib.toShortString()}\t TB"
|
||||
|
||||
val pib = tib / divisor
|
||||
if (pib < threshold)
|
||||
return "${pib.toShortString()} PB"
|
||||
return "${pib.toShortString()}\t PB"
|
||||
|
||||
return "∞"
|
||||
}
|
||||
|
||||
private fun Float.toShortString(): String {
|
||||
val s = toString()
|
||||
val s = "%.2f".format(this)
|
||||
if (s.length <= 4)
|
||||
return s
|
||||
return s.substring(0, 4).removeSuffix(".")
|
||||
|
||||
@@ -3,14 +3,14 @@ package com.v2ray.ang.receiver
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
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
|
||||
import org.jetbrains.anko.toast
|
||||
|
||||
class WidgetProvider : AppWidgetProvider() {
|
||||
/**
|
||||
@@ -18,11 +18,20 @@ class WidgetProvider : AppWidgetProvider() {
|
||||
*/
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
|
||||
}
|
||||
|
||||
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
|
||||
val intent = Intent(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
|
||||
val intent = Intent(context, WidgetProvider::class.java)
|
||||
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
|
||||
if (isRunning) {
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme)
|
||||
} else {
|
||||
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_grey)
|
||||
}
|
||||
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
|
||||
@@ -30,21 +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)
|
||||
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,15 @@
|
||||
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
|
||||
|
||||
fun vpnSendFd()
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
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 vpnSendFd() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendFd(): Long {
|
||||
val serviceControl = serviceControl?.get() ?: return -1
|
||||
try {
|
||||
serviceControl.vpnSendFd()
|
||||
} catch (e: Exception) {
|
||||
Log.d(serviceControl.getService().packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
fun startV2rayPoint() {
|
||||
val service = serviceControl?.get()?.getService() ?: return
|
||||
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, "")
|
||||
|
||||
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,69 +1,27 @@
|
||||
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.net.VpnService
|
||||
import android.os.*
|
||||
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.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 libv2ray.Libv2ray
|
||||
import libv2ray.V2RayVPNServiceSupportsSet
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import java.net.InetAddress
|
||||
import java.io.IOException
|
||||
import java.io.File
|
||||
import java.io.FileDescriptor
|
||||
import java.io.FileInputStream
|
||||
import java.lang.ref.SoftReference
|
||||
import android.os.Build
|
||||
import android.annotation.TargetApi
|
||||
import android.util.Log
|
||||
import go.Seq
|
||||
import org.jetbrains.anko.doAsync
|
||||
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
|
||||
|
||||
fun startV2Ray(context: Context) {
|
||||
val intent = Intent(context.applicationContext, V2RayVpnService::class.java)
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
|
||||
private lateinit var configContent: String
|
||||
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
|
||||
@@ -74,27 +32,32 @@ class V2RayVpnService : VpnService() {
|
||||
*
|
||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
|
||||
*/
|
||||
@TargetApi(28)
|
||||
private val defaultNetworkRequest = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||
.build()
|
||||
|
||||
@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 }
|
||||
@TargetApi(28)
|
||||
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
|
||||
// it's a good idea to refresh capabilities
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
setUnderlyingNetworks(null)
|
||||
|
||||
@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?) {
|
||||
// it's a good idea to refresh capabilities
|
||||
setUnderlyingNetworks(arrayOf(network))
|
||||
}
|
||||
override fun onLost(network: Network) {
|
||||
setUnderlyingNetworks(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var listeningForDefaultNetwork = false
|
||||
|
||||
override fun onCreate() {
|
||||
@@ -102,8 +65,7 @@ class V2RayVpnService : VpnService() {
|
||||
|
||||
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
|
||||
StrictMode.setThreadPolicy(policy)
|
||||
v2rayPoint.packageName = Utils.packagePath(applicationContext)
|
||||
Seq.setContext(applicationContext)
|
||||
V2RayServiceManager.serviceControl = SoftReference(this)
|
||||
}
|
||||
|
||||
override fun onRevoke() {
|
||||
@@ -117,12 +79,12 @@ class V2RayVpnService : VpnService() {
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
cancelNotification()
|
||||
V2RayServiceManager.cancelNotification()
|
||||
}
|
||||
|
||||
fun setup(parameters: String) {
|
||||
private fun setup(parameters: String) {
|
||||
|
||||
val prepare = VpnService.prepare(this)
|
||||
val prepare = prepare(this)
|
||||
if (prepare != null) {
|
||||
return
|
||||
}
|
||||
@@ -131,6 +93,7 @@ class V2RayVpnService : VpnService() {
|
||||
// Configure a builder while parsing the parameters.
|
||||
val builder = Builder()
|
||||
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
|
||||
val routingMode = defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
|
||||
|
||||
parameters.split(" ")
|
||||
.map { it.split(",") }
|
||||
@@ -139,7 +102,20 @@ class V2RayVpnService : VpnService() {
|
||||
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
|
||||
's' -> builder.addSearchDomain(it[1])
|
||||
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
|
||||
'r' -> builder.addRoute(it[1], Integer.parseInt(it[2]))
|
||||
'r' -> {
|
||||
if (routingMode == "1" || routingMode == "3") {
|
||||
if (it[1] == "::") { //not very elegant, should move Vpn setting in Kotlin, simplify go code
|
||||
builder.addRoute("2000::", 3)
|
||||
} else {
|
||||
resources.getStringArray(R.array.bypass_private_ip_address).forEach { cidr ->
|
||||
val addr = cidr.split('/')
|
||||
builder.addRoute(addr[0], addr[1].toInt())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
builder.addRoute(it[1], Integer.parseInt(it[2]))
|
||||
}
|
||||
}
|
||||
'd' -> builder.addDnsServer(it[1])
|
||||
}
|
||||
}
|
||||
@@ -173,25 +149,24 @@ class V2RayVpnService : VpnService() {
|
||||
try {
|
||||
mInterface.close()
|
||||
} catch (ignored: Exception) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = true
|
||||
}
|
||||
|
||||
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()
|
||||
sendFd()
|
||||
startSpeedNotification()
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
stopV2Ray(true)
|
||||
}
|
||||
|
||||
fun sendFd() {
|
||||
private fun sendFd() {
|
||||
val fd = mInterface.fileDescriptor
|
||||
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
|
||||
|
||||
@@ -199,7 +174,7 @@ class V2RayVpnService : VpnService() {
|
||||
var tries = 0
|
||||
while (true) try {
|
||||
Thread.sleep(50L shl tries)
|
||||
Log.d(packageName, "sendFd tries: " + tries.toString())
|
||||
Log.d(packageName, "sendFd tries: $tries")
|
||||
LocalSocket().use { localSocket ->
|
||||
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
|
||||
localSocket.setFileDescriptorsForSend(arrayOf(fd))
|
||||
@@ -215,74 +190,24 @@ 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 >= 28) {
|
||||
if (listeningForDefaultNetwork) {
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = false
|
||||
}
|
||||
}
|
||||
if (v2rayPoint.isRunning) {
|
||||
try {
|
||||
v2rayPoint.stopLoop()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && listeningForDefaultNetwork) {
|
||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
||||
listeningForDefaultNetwork = false
|
||||
}
|
||||
|
||||
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
|
||||
@@ -293,207 +218,29 @@ 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) {
|
||||
if (mBuilder != null) {
|
||||
mBuilder?.setContentTitle(contentText)
|
||||
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)) {
|
||||
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
|
||||
var last_zero_speed = false
|
||||
|
||||
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
|
||||
.subscribe {
|
||||
val uplink = v2rayPoint.queryStats("socks", "uplink")
|
||||
val downlink = v2rayPoint.queryStats("socks", "downlink")
|
||||
val zero_speed = (uplink == 0L && downlink == 0L)
|
||||
if (!zero_speed || !last_zero_speed) {
|
||||
updateNotification("${cf_name} • ${(uplink / 3).toSpeedString()}↑ ${(downlink / 3).toSpeedString()}↓")
|
||||
}
|
||||
last_zero_speed = zero_speed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun stopSpeedNotification() {
|
||||
if (mSubscription != null) {
|
||||
mSubscription?.unsubscribe() //stop queryStats
|
||||
mSubscription = null
|
||||
|
||||
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
|
||||
updateNotification(cf_name)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class V2RayCallback : V2RayVPNServiceSupportsSet {
|
||||
override fun shutdown(): Long {
|
||||
// called by go
|
||||
// shutdown the whole vpn service
|
||||
try {
|
||||
this@V2RayVpnService.shutdown()
|
||||
return 0
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
override fun prepare(): Long {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun protect(l: Long) = (if (this@V2RayVpnService.protect(l.toInt())) 0 else 1).toLong()
|
||||
|
||||
override fun onEmitStatus(l: Long, s: String?): Long {
|
||||
//Logger.d(s)
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun setup(s: String): Long {
|
||||
//Logger.d(s)
|
||||
try {
|
||||
this@V2RayVpnService.setup(s)
|
||||
return 0
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
override fun sendFd(): Long {
|
||||
try {
|
||||
this@V2RayVpnService.sendFd()
|
||||
} catch (e: Exception) {
|
||||
Log.d(packageName, e.toString())
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService)
|
||||
|
||||
private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() {
|
||||
internal var mReference: SoftReference<V2RayVpnService> = SoftReference(vpnService)
|
||||
|
||||
override fun onReceive(ctx: Context?, intent: Intent?) {
|
||||
val vpnService = mReference.get()
|
||||
when (intent?.getIntExtra("key", 0)) {
|
||||
AppConfig.MSG_REGISTER_CLIENT -> {
|
||||
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
|
||||
|
||||
val isRunning = vpnService?.v2rayPoint!!.isRunning
|
||||
&& VpnService.prepare(vpnService) == null
|
||||
if (isRunning) {
|
||||
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "")
|
||||
} else {
|
||||
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "")
|
||||
}
|
||||
}
|
||||
AppConfig.MSG_UNREGISTER_CLIENT -> {
|
||||
// vpnService?.mMsgSend = null
|
||||
}
|
||||
AppConfig.MSG_STATE_START -> {
|
||||
//nothing to do
|
||||
}
|
||||
AppConfig.MSG_STATE_STOP -> {
|
||||
vpnService?.stopV2Ray()
|
||||
}
|
||||
AppConfig.MSG_STATE_RESTART -> {
|
||||
vpnService?.startV2ray()
|
||||
}
|
||||
}
|
||||
|
||||
when (intent?.action) {
|
||||
Intent.ACTION_SCREEN_OFF -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
|
||||
vpnService?.stopSpeedNotification()
|
||||
}
|
||||
Intent.ACTION_SCREEN_ON -> {
|
||||
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
|
||||
vpnService?.startSpeedNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun vpnSendFd() {
|
||||
sendFd()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ abstract class BaseDrawerActivity : BaseActivity() {
|
||||
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
|
||||
var activityClass: Class<*>? = null
|
||||
when (mItemToOpenWhenDrawerCloses) {
|
||||
R.id.server_profile -> activityClass = MainActivity::class.java
|
||||
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
|
||||
R.id.settings -> activityClass = SettingsActivity::class.java
|
||||
R.id.logcat -> {
|
||||
@@ -195,9 +194,7 @@ abstract class BaseDrawerActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
if (MainActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.server_profile)
|
||||
} else if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.sub_setting)
|
||||
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
|
||||
navigationView.setCheckedItem(R.id.settings)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@@ -24,15 +26,22 @@ class LogcatActivity : BaseActivity() {
|
||||
title = getString(R.string.title_logcat)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
logcat()
|
||||
logcat(false)
|
||||
}
|
||||
|
||||
private fun logcat() {
|
||||
private fun logcat(shouldFlushLog: Boolean) {
|
||||
|
||||
try {
|
||||
pb_waiting.visibility = View.VISIBLE
|
||||
|
||||
doAsync {
|
||||
if (shouldFlushLog) {
|
||||
val lst = LinkedHashSet<String>()
|
||||
lst.add("logcat")
|
||||
lst.add("-c")
|
||||
val process = Runtime.getRuntime().exec(lst.toTypedArray())
|
||||
process.waitFor()
|
||||
}
|
||||
val lst = LinkedHashSet<String>()
|
||||
lst.add("logcat")
|
||||
lst.add("-d")
|
||||
@@ -49,9 +58,11 @@ class LogcatActivity : BaseActivity() {
|
||||
tv_logcat.text = allText
|
||||
tv_logcat.movementMethod = ScrollingMovementMethod()
|
||||
pb_waiting.visibility = View.GONE
|
||||
Handler(Looper.getMainLooper()).post { sv_logcat.fullScroll(View.FOCUS_DOWN) }
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +77,10 @@ class LogcatActivity : BaseActivity() {
|
||||
toast(R.string.toast_success)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.delete -> {
|
||||
logcat(true)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,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.extension.defaultDPreference
|
||||
//import com.v2ray.ang.InappBuyActivity
|
||||
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 {
|
||||
@@ -58,6 +63,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
|
||||
private val adapter by lazy { MainRecyclerAdapter(this) }
|
||||
private var mItemTouchHelper: ItemTouchHelper? = null
|
||||
private val testingJobs = ArrayList<Job>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -68,13 +74,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 {
|
||||
@@ -107,6 +115,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() {
|
||||
@@ -162,8 +171,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
|
||||
}
|
||||
REQUEST_FILE_CHOOSER -> {
|
||||
if (resultCode == RESULT_OK) {
|
||||
val uri = data!!.data
|
||||
val uri = data?.data
|
||||
if (resultCode == RESULT_OK && uri != null) {
|
||||
readContentFromUri(uri)
|
||||
}
|
||||
}
|
||||
@@ -240,19 +249,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
}
|
||||
|
||||
R.id.ping_all -> {
|
||||
testingJobs.forEach {
|
||||
it.cancel()
|
||||
}
|
||||
testingJobs.clear()
|
||||
Utils.closeAllTcpSockets()
|
||||
for (k in 0 until configs.vmess.count()) {
|
||||
configs.vmess[k].testResult = ""
|
||||
adapter.updateConfigList()
|
||||
}
|
||||
for (k in 0 until configs.vmess.count()) {
|
||||
if (configs.vmess[k].configType != AppConfig.EConfigType.Custom) {
|
||||
doAsync {
|
||||
configs.vmess[k].testResult = Utils.tcping(configs.vmess[k].address, configs.vmess[k].port)
|
||||
uiThread {
|
||||
var serverAddress = configs.vmess[k].address
|
||||
var serverPort = configs.vmess[k].port
|
||||
if (configs.vmess[k].configType == EConfigType.CUSTOM.value) {
|
||||
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(applicationContext, configs.vmess[k].guid)
|
||||
?: continue
|
||||
serverAddress = serverOutbound.getServerAddress() ?: continue
|
||||
serverPort = serverOutbound.getServerPort() ?: continue
|
||||
}
|
||||
testingJobs.add(GlobalScope.launch(Dispatchers.IO) {
|
||||
configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing
|
||||
it.testResult = Utils.tcping(serverAddress, serverPort)
|
||||
val myJob = coroutineContext[Job]
|
||||
launch(Dispatchers.Main) {
|
||||
testingJobs.remove(myJob)
|
||||
adapter.updateSelectedItem(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -442,9 +466,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
.subscribe {
|
||||
if (it) {
|
||||
try {
|
||||
val inputStream = contentResolver.openInputStream(uri)
|
||||
val configText = inputStream.bufferedReader().readText()
|
||||
importCustomizeConfig(configText)
|
||||
contentResolver.openInputStream(uri).use {
|
||||
val configText = it?.bufferedReader()?.readText()
|
||||
importCustomizeConfig(configText)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -570,4 +595,4 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,19 @@ 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.helper.ItemTouchHelperAdapter
|
||||
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
|
||||
import com.v2ray.ang.util.AngConfigManager
|
||||
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
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
|
||||
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
|
||||
, ItemTouchHelperAdapter {
|
||||
@@ -49,7 +51,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
|
||||
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
|
||||
if (holder is MainViewHolder) {
|
||||
val configType = configs.vmess[position].configType
|
||||
val configType = EConfigType.fromInt(configs.vmess[position].configType)
|
||||
val remarks = configs.vmess[position].remarks
|
||||
val subid = configs.vmess[position].subid
|
||||
val address = configs.vmess[position].address
|
||||
@@ -67,39 +69,40 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
holder.subid.text = "S"
|
||||
}
|
||||
|
||||
if (configType == AppConfig.EConfigType.Vmess) {
|
||||
holder.type.text = "vmess"
|
||||
holder.statistics.text = "$address : $port"
|
||||
holder.layout_share.visibility = View.VISIBLE
|
||||
} else if (configType == AppConfig.EConfigType.Custom) {
|
||||
var shareOptions = share_method.asList()
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
holder.type.text = mActivity.getString(R.string.server_customize_config)
|
||||
holder.statistics.text = ""//mActivity.getString(R.string.server_customize_config)
|
||||
holder.layout_share.visibility = View.INVISIBLE
|
||||
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
|
||||
holder.type.text = "shadowsocks"
|
||||
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
|
||||
if (serverOutbound == null) {
|
||||
holder.statistics.text = ""
|
||||
} else {
|
||||
holder.statistics.text = "${serverOutbound.getServerAddress()} : ${serverOutbound.getServerPort()}"
|
||||
}
|
||||
shareOptions = shareOptions.takeLast(1)
|
||||
} else {
|
||||
holder.type.text = configType?.name?.toLowerCase()
|
||||
holder.statistics.text = "$address : $port"
|
||||
holder.layout_share.visibility = View.VISIBLE
|
||||
} else if (configType == AppConfig.EConfigType.Socks) {
|
||||
holder.type.text = "socks"
|
||||
holder.statistics.text = "$address : $port"
|
||||
holder.layout_share.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
holder.layout_share.setOnClickListener {
|
||||
mActivity.selector(null, share_method.asList()) { dialogInterface, i ->
|
||||
mActivity.selector(null, shareOptions) { dialogInterface, i ->
|
||||
try {
|
||||
when (i) {
|
||||
0 -> {
|
||||
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
|
||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
shareFullContent(position)
|
||||
} else {
|
||||
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
|
||||
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
|
||||
|
||||
mActivity.alert {
|
||||
customView {
|
||||
linearLayout {
|
||||
addView(iv)
|
||||
mActivity.alert {
|
||||
customView {
|
||||
linearLayout {
|
||||
addView(iv)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
1 -> {
|
||||
if (AngConfigManager.share2Clipboard(position) == 0) {
|
||||
@@ -108,15 +111,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
else ->
|
||||
mActivity.toast("else")
|
||||
2 -> shareFullContent(position)
|
||||
else -> mActivity.toast("else")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@@ -125,13 +121,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
|
||||
holder.layout_edit.setOnClickListener {
|
||||
if (configType == AppConfig.EConfigType.Vmess) {
|
||||
if (configType == EConfigType.VMESS) {
|
||||
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable)
|
||||
} else if (configType == AppConfig.EConfigType.Custom) {
|
||||
} else if (configType == EConfigType.CUSTOM) {
|
||||
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
|
||||
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
|
||||
} else if (configType == EConfigType.SHADOWSOCKS) {
|
||||
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable)
|
||||
} else if (configType == AppConfig.EConfigType.Socks) {
|
||||
} else if (configType == EConfigType.SOCKS) {
|
||||
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
|
||||
}
|
||||
}
|
||||
@@ -176,6 +172,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFullContent(position: Int) {
|
||||
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
|
||||
mActivity.toast(R.string.toast_success)
|
||||
} else {
|
||||
mActivity.toast(R.string.toast_failure)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
|
||||
when (viewType) {
|
||||
VIEW_TYPE_ITEM ->
|
||||
@@ -265,4 +269,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
|
||||
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onItemMoveCompleted() {
|
||||
AngConfigManager.storeConfigFile()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,10 +119,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
when (requestCode) {
|
||||
REQUEST_FILE_CHOOSER ->
|
||||
if (resultCode == RESULT_OK) {
|
||||
REQUEST_FILE_CHOOSER -> {
|
||||
val uri = data?.data
|
||||
if (resultCode == RESULT_OK && uri != null) {
|
||||
try {
|
||||
val uri = data!!.data
|
||||
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
|
||||
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
|
||||
finished(text)
|
||||
@@ -131,6 +131,7 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
|
||||
toast(e.message.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
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.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 +33,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 +46,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 +65,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>()
|
||||
activity?.startActivity<PerAppProxyActivity>()
|
||||
perAppProxy.isChecked = true
|
||||
true
|
||||
}
|
||||
@@ -117,7 +106,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 +123,11 @@ class SettingsActivity : BaseActivity() {
|
||||
true
|
||||
}
|
||||
|
||||
routingCustom.onClick {
|
||||
routingCustom.setOnPreferenceClickListener {
|
||||
if (isRunning())
|
||||
Utils.stopVService(activity)
|
||||
startActivity<RoutingSettingsActivity>()
|
||||
Utils.stopVService(requireContext())
|
||||
activity?.startActivity<RoutingSettingsActivity>()
|
||||
false
|
||||
}
|
||||
|
||||
forwardIpv6.setOnPreferenceClickListener {
|
||||
@@ -153,7 +143,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 +152,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 +161,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,16 +202,13 @@ 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)
|
||||
remoteDns.summary = defaultSharedPreferences.getString(PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = defaultSharedPreferences.getString(PREF_DOMESTIC_DNS, "")
|
||||
updatePerAppProxy(activity?.defaultSharedPreferences?.getString(AppConfig.PREF_MODE, "VPN"))
|
||||
remoteDns.summary = activity?.defaultSharedPreferences?.getString(PREF_REMOTE_DNS, "")
|
||||
domesticDns.summary = activity?.defaultSharedPreferences?.getString(PREF_DOMESTIC_DNS, "")
|
||||
|
||||
if (remoteDns.summary == "") {
|
||||
remoteDns.summary = AppConfig.DNS_AGENT
|
||||
@@ -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?) {
|
||||
val preference = activity?.defaultSharedPreferences ?: return
|
||||
if (mode == "VPN") {
|
||||
perAppProxy.isEnabled = true
|
||||
perAppProxy.isChecked = preference.getBoolean(PREF_PER_APP_PROXY, false)
|
||||
} else {
|
||||
perAppProxy.isEnabled = false
|
||||
perAppProxy.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
fun onModeHelpClicked(view: View) {
|
||||
Utils.openUri(this, AppConfig.v2rayNGWikiMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.v2ray.ang.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
@@ -15,13 +14,11 @@ import com.v2ray.ang.AppConfig.SS_PROTOCOL
|
||||
import com.v2ray.ang.AppConfig.VMESS_PROTOCOL
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.AngConfig
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.dto.VmessQRCode
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import org.jetbrains.anko.toast
|
||||
import java.net.URI
|
||||
import java.net.URLDecoder
|
||||
import java.util.*
|
||||
import java.net.*
|
||||
import java.math.BigInteger
|
||||
|
||||
object AngConfigManager {
|
||||
private lateinit var app: AngApplication
|
||||
@@ -69,7 +66,7 @@ object AngConfigManager {
|
||||
fun addServer(vmess: AngConfig.VmessBean, index: Int): Int {
|
||||
try {
|
||||
vmess.configVersion = 2
|
||||
vmess.configType = AppConfig.EConfigType.Vmess
|
||||
vmess.configType = EConfigType.VMESS.value
|
||||
|
||||
if (index >= 0) {
|
||||
//edit
|
||||
@@ -104,16 +101,7 @@ object AngConfigManager {
|
||||
angConfig.vmess.removeAt(index)
|
||||
|
||||
//移除的是活动的
|
||||
if (angConfig.index == index) {
|
||||
if (angConfig.vmess.count() > 0) {
|
||||
angConfig.index = 0
|
||||
} else {
|
||||
angConfig.index = -1
|
||||
}
|
||||
} else if (index < angConfig.index)//移除活动之前的
|
||||
{
|
||||
angConfig.index--
|
||||
}
|
||||
adjustIndexForRemovalAt(index)
|
||||
|
||||
storeConfigFile()
|
||||
} catch (e: Exception) {
|
||||
@@ -123,6 +111,19 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun adjustIndexForRemovalAt(index: Int) {
|
||||
if (angConfig.index == index) {
|
||||
if (angConfig.vmess.count() > 0) {
|
||||
angConfig.index = 0
|
||||
} else {
|
||||
angConfig.index = -1
|
||||
}
|
||||
} else if (index < angConfig.index)//移除活动之前的
|
||||
{
|
||||
angConfig.index--
|
||||
}
|
||||
}
|
||||
|
||||
fun swapServer(fromPosition: Int, toPosition: Int): Int {
|
||||
try {
|
||||
Collections.swap(angConfig.vmess, fromPosition, toPosition)
|
||||
@@ -133,7 +134,7 @@ object AngConfigManager {
|
||||
} else if (index == toPosition) {
|
||||
angConfig.index = fromPosition
|
||||
}
|
||||
storeConfigFile()
|
||||
//storeConfigFile()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return -1
|
||||
@@ -208,14 +209,14 @@ object AngConfigManager {
|
||||
return app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
|
||||
}
|
||||
|
||||
fun currConfigType(): Int {
|
||||
fun currConfigType(): EConfigType? {
|
||||
if (angConfig.index < 0
|
||||
|| angConfig.vmess.count() <= 0
|
||||
|| angConfig.index > angConfig.vmess.count() - 1
|
||||
) {
|
||||
return -1
|
||||
return null
|
||||
}
|
||||
return angConfig.vmess[angConfig.index].configType
|
||||
return EConfigType.fromInt(angConfig.vmess[angConfig.index].configType)
|
||||
}
|
||||
|
||||
fun currConfigName(): String {
|
||||
@@ -241,7 +242,7 @@ object AngConfigManager {
|
||||
/**
|
||||
* import config form qrcode or...
|
||||
*/
|
||||
fun importConfig(server: String?, subid: String): Int {
|
||||
fun importConfig(server: String?, subid: String, removedSelectedServer: AngConfig.VmessBean?): Int {
|
||||
try {
|
||||
if (server == null || TextUtils.isEmpty(server)) {
|
||||
return R.string.toast_none_data
|
||||
@@ -252,7 +253,11 @@ object AngConfigManager {
|
||||
if (server.startsWith(VMESS_PROTOCOL)) {
|
||||
|
||||
val indexSplit = server.indexOf("?")
|
||||
if (indexSplit > 0) {
|
||||
val newVmess = tryParseNewVmess(server)
|
||||
if (newVmess != null) {
|
||||
vmess = newVmess
|
||||
vmess.subid = subid
|
||||
} else if (indexSplit > 0) {
|
||||
vmess = ResolveVmess4Kitsunebi(server)
|
||||
} else {
|
||||
|
||||
@@ -271,7 +276,7 @@ object AngConfigManager {
|
||||
return R.string.toast_incorrect_protocol
|
||||
}
|
||||
|
||||
vmess.configType = AppConfig.EConfigType.Vmess
|
||||
vmess.configType = EConfigType.VMESS.value
|
||||
vmess.security = "auto"
|
||||
vmess.network = "tcp"
|
||||
vmess.headerType = "none"
|
||||
@@ -361,6 +366,12 @@ object AngConfigManager {
|
||||
} else {
|
||||
return R.string.toast_incorrect_protocol
|
||||
}
|
||||
if (removedSelectedServer != null &&
|
||||
vmess.subid.equals(removedSelectedServer.subid) &&
|
||||
vmess.address.equals(removedSelectedServer.address) &&
|
||||
vmess.port.equals(removedSelectedServer.port)) {
|
||||
setActiveServer(configs.vmess.count() - 1)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return -1
|
||||
@@ -368,6 +379,61 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
|
||||
fun tryParseNewVmess(uri: String): AngConfig.VmessBean? {
|
||||
return runCatching {
|
||||
val uri = URI(uri)
|
||||
check(uri.scheme == "vmess")
|
||||
val (_, protocol, tlsStr, uuid, alterId) =
|
||||
Regex("(tcp|http|ws|kcp|quic)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})-([0-9]+)")
|
||||
.matchEntire(uri.userInfo)?.groupValues
|
||||
?: error("parse user info fail.")
|
||||
val tls = tlsStr.isNotBlank()
|
||||
val queryParam = uri.rawQuery.split("&")
|
||||
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
|
||||
.toMap()
|
||||
val vmess = AngConfig.VmessBean()
|
||||
vmess.address = uri.host
|
||||
vmess.port = uri.port
|
||||
vmess.id = uuid
|
||||
vmess.alterId = alterId.toInt()
|
||||
vmess.streamSecurity = if (tls) "tls" else ""
|
||||
vmess.remarks = uri.fragment
|
||||
vmess.security = "auto"
|
||||
|
||||
// TODO: allowInsecure not supported
|
||||
|
||||
when (protocol) {
|
||||
"tcp" -> {
|
||||
vmess.network = "tcp"
|
||||
vmess.headerType = queryParam["type"] ?: "none"
|
||||
vmess.requestHost = queryParam["host"] ?: ""
|
||||
}
|
||||
"http" -> {
|
||||
vmess.network = "h2"
|
||||
vmess.path = queryParam["path"]?.takeIf { it.trim() != "/" } ?: ""
|
||||
vmess.requestHost = queryParam["host"]?.split("|")?.get(0) ?: ""
|
||||
}
|
||||
"ws" -> {
|
||||
vmess.network = "ws"
|
||||
vmess.path = queryParam["path"]?.takeIf { it.trim() != "/" } ?: ""
|
||||
vmess.requestHost = queryParam["host"]?.split("|")?.get(0) ?: ""
|
||||
}
|
||||
"kcp" -> {
|
||||
vmess.network = "kcp"
|
||||
vmess.headerType = queryParam["type"] ?: "none"
|
||||
vmess.path = queryParam["seed"] ?: ""
|
||||
}
|
||||
"quic" -> {
|
||||
vmess.network = "quic"
|
||||
vmess.requestHost = queryParam["security"] ?: "none"
|
||||
vmess.headerType = queryParam["type"] ?: "none"
|
||||
vmess.path = queryParam["key"] ?: ""
|
||||
}
|
||||
}
|
||||
vmess
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
private fun ResolveVmess4Kitsunebi(server: String): AngConfig.VmessBean {
|
||||
|
||||
val vmess = AngConfig.VmessBean()
|
||||
@@ -413,7 +479,7 @@ object AngConfigManager {
|
||||
}
|
||||
|
||||
val vmess = angConfig.vmess[index]
|
||||
if (angConfig.vmess[index].configType == AppConfig.EConfigType.Vmess) {
|
||||
if (angConfig.vmess[index].configType == EConfigType.VMESS.value) {
|
||||
|
||||
val vmessQRCode = VmessQRCode()
|
||||
vmessQRCode.v = vmess.configVersion.toString()
|
||||
@@ -431,7 +497,7 @@ object AngConfigManager {
|
||||
val conf = VMESS_PROTOCOL + Utils.encode(json)
|
||||
|
||||
return conf
|
||||
} else if (angConfig.vmess[index].configType == AppConfig.EConfigType.Shadowsocks) {
|
||||
} else if (angConfig.vmess[index].configType == EConfigType.SHADOWSOCKS.value) {
|
||||
val remark = "#" + Utils.urlEncode(vmess.remarks)
|
||||
val url = String.format("%s:%s@%s:%s",
|
||||
vmess.security,
|
||||
@@ -439,7 +505,7 @@ object AngConfigManager {
|
||||
vmess.address,
|
||||
vmess.port)
|
||||
return SS_PROTOCOL + Utils.encode(url) + remark
|
||||
} else if (angConfig.vmess[index].configType == AppConfig.EConfigType.Socks) {
|
||||
} else if (angConfig.vmess[index].configType == EConfigType.SOCKS.value) {
|
||||
val remark = "#" + Utils.urlEncode(vmess.remarks)
|
||||
val url = String.format("%s:%s",
|
||||
vmess.address,
|
||||
@@ -548,7 +614,7 @@ object AngConfigManager {
|
||||
//add
|
||||
val vmess = AngConfig.VmessBean()
|
||||
vmess.configVersion = 2
|
||||
vmess.configType = AppConfig.EConfigType.Custom
|
||||
vmess.configType = EConfigType.CUSTOM.value
|
||||
vmess.guid = guid
|
||||
vmess.remarks = vmess.guid
|
||||
|
||||
@@ -651,7 +717,7 @@ object AngConfigManager {
|
||||
fun addCustomServer(vmess: AngConfig.VmessBean, index: Int): Int {
|
||||
try {
|
||||
vmess.configVersion = 2
|
||||
vmess.configType = AppConfig.EConfigType.Custom
|
||||
vmess.configType = EConfigType.CUSTOM.value
|
||||
|
||||
if (index >= 0) {
|
||||
//edit
|
||||
@@ -676,7 +742,7 @@ object AngConfigManager {
|
||||
fun addShadowsocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
|
||||
try {
|
||||
vmess.configVersion = 2
|
||||
vmess.configType = AppConfig.EConfigType.Shadowsocks
|
||||
vmess.configType = EConfigType.SHADOWSOCKS.value
|
||||
|
||||
if (index >= 0) {
|
||||
//edit
|
||||
@@ -701,7 +767,7 @@ object AngConfigManager {
|
||||
fun addSocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
|
||||
try {
|
||||
vmess.configVersion = 2
|
||||
vmess.configType = AppConfig.EConfigType.Socks
|
||||
vmess.configType = EConfigType.SOCKS.value
|
||||
|
||||
if (index >= 0) {
|
||||
//edit
|
||||
@@ -728,6 +794,17 @@ object AngConfigManager {
|
||||
if (servers == null) {
|
||||
return 0
|
||||
}
|
||||
val removedSelectedServer =
|
||||
if (!TextUtils.isEmpty(subid)) {
|
||||
configs.vmess.getOrNull(configs.index)?.let {
|
||||
if (it.subid == subid) {
|
||||
return@let it
|
||||
}
|
||||
return@let null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
removeServerViaSubid(subid)
|
||||
|
||||
// var servers = server
|
||||
@@ -738,7 +815,7 @@ object AngConfigManager {
|
||||
var count = 0
|
||||
servers.lines()
|
||||
.forEach {
|
||||
val resId = importConfig(it, subid)
|
||||
val resId = importConfig(it, subid, removedSelectedServer)
|
||||
if (resId == 0) {
|
||||
count++
|
||||
}
|
||||
@@ -778,6 +855,7 @@ object AngConfigManager {
|
||||
for (k in configs.vmess.count() - 1 downTo 0) {
|
||||
if (configs.vmess[k].subid.equals(subid)) {
|
||||
angConfig.vmess.removeAt(k)
|
||||
adjustIndexForRemovalAt(k)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,4 +901,4 @@ object AngConfigManager {
|
||||
return 0
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ object AppManagerUtil {
|
||||
val apps = ArrayList<AppInfo>()
|
||||
|
||||
for (pkg in packages) {
|
||||
if (!pkg.hasInternetPermission) continue
|
||||
if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
|
||||
|
||||
val applicationInfo = pkg.applicationInfo
|
||||
|
||||
@@ -40,4 +40,4 @@ object AppManagerUtil {
|
||||
val permissions = requestedPermissions
|
||||
return permissions?.any { it == Manifest.permission.INTERNET } ?: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,43 +11,35 @@ 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.content.res.AssetManager
|
||||
import android.net.Uri
|
||||
import android.os.SystemClock
|
||||
import android.text.TextUtils
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.util.Log
|
||||
import android.util.Patterns
|
||||
import android.view.View
|
||||
import android.webkit.URLUtil
|
||||
import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.responseLength
|
||||
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.android.synthetic.main.activity_logcat.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import me.dozen.dpreference.DPreference
|
||||
import org.jetbrains.anko.toast
|
||||
import org.jetbrains.anko.uiThread
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.net.*
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
import java.math.BigInteger
|
||||
import java.util.concurrent.TimeUnit
|
||||
import libv2ray.Libv2ray
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
|
||||
object Utils {
|
||||
|
||||
val tcpTestingSockets = ArrayList<Socket?>()
|
||||
|
||||
/**
|
||||
* convert string to editalbe for kotlin
|
||||
*
|
||||
@@ -102,7 +94,7 @@ object Utils {
|
||||
try {
|
||||
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clipData = ClipData.newPlainText(null, content)
|
||||
cmb.primaryClip = clipData
|
||||
cmb.setPrimaryClip(clipData)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -280,32 +272,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,7 +292,7 @@ object Utils {
|
||||
if (AngConfigManager.genStoreV2rayConfig(-1)) {
|
||||
val configContent = AngConfigManager.currGeneratedV2rayConfig()
|
||||
val configType = AngConfigManager.currConfigType()
|
||||
if (configType == AppConfig.EConfigType.Custom) {
|
||||
if (configType == EConfigType.CUSTOM) {
|
||||
try {
|
||||
Libv2ray.testConfig(configContent)
|
||||
} catch (e: Exception) {
|
||||
@@ -328,7 +300,7 @@ object Utils {
|
||||
return false
|
||||
}
|
||||
}
|
||||
V2RayVpnService.startV2Ray(context)
|
||||
V2RayServiceManager.startV2Ray(context, context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN"))
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -448,7 +420,6 @@ object Utils {
|
||||
return path
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* readTextFromAssets
|
||||
*/
|
||||
@@ -483,10 +454,13 @@ object Utils {
|
||||
/**
|
||||
* tcping
|
||||
*/
|
||||
fun tcping(url: String, port: Int): String {
|
||||
suspend fun tcping(url: String, port: Int): String {
|
||||
var time = -1L
|
||||
for (k in 0 until 2) {
|
||||
val one = socketConnectTime(url, port)
|
||||
if (!coroutineContext.isActive) {
|
||||
break
|
||||
}
|
||||
if (one != -1L )
|
||||
if(time == -1L || one < time) {
|
||||
time = one
|
||||
@@ -497,19 +471,35 @@ object Utils {
|
||||
|
||||
fun socketConnectTime(url: String, port: Int): Long {
|
||||
try {
|
||||
val socket = Socket()
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.add(socket)
|
||||
}
|
||||
val start = System.currentTimeMillis()
|
||||
val socket = Socket(url, port)
|
||||
socket.connect(InetSocketAddress(url, port))
|
||||
val time = System.currentTimeMillis() - start
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.remove(socket)
|
||||
}
|
||||
socket.close()
|
||||
return time
|
||||
} catch (e: UnknownHostException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun closeAllTcpSockets() {
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.forEach {
|
||||
it?.close()
|
||||
}
|
||||
tcpTestingSockets.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
@@ -9,12 +10,15 @@ import com.v2ray.ang.AngApplication
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.AngConfig.VmessBean
|
||||
import com.v2ray.ang.dto.V2rayConfig
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import com.v2ray.ang.ui.SettingsActivity
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import org.json.JSONArray
|
||||
import com.google.gson.JsonObject
|
||||
import com.v2ray.ang.dto.EConfigType
|
||||
import com.v2ray.ang.extension.defaultDPreference
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashSet
|
||||
|
||||
object V2rayConfigUtil {
|
||||
private val requestObj: JsonObject by lazy {
|
||||
@@ -41,19 +45,10 @@ object V2rayConfigUtil {
|
||||
// return result
|
||||
// }
|
||||
|
||||
if (vmess.configType == AppConfig.EConfigType.Vmess) {
|
||||
result = getV2rayConfigType1(app, vmess)
|
||||
} else if (vmess.configType == AppConfig.EConfigType.Custom) {
|
||||
if (vmess.configType == EConfigType.CUSTOM.value) {
|
||||
result = getV2rayConfigType2(app, vmess)
|
||||
} else if (vmess.configType == AppConfig.EConfigType.Shadowsocks) {
|
||||
} else {
|
||||
result = getV2rayConfigType1(app, vmess)
|
||||
} else if (vmess.configType == AppConfig.EConfigType.Socks) {
|
||||
result = getV2rayConfigType1(app, vmess)
|
||||
}
|
||||
|
||||
val domainName = parseDomainName(result.content)
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
|
||||
}
|
||||
|
||||
Log.d("V2rayConfigUtilGoLog", result.content)
|
||||
@@ -64,6 +59,22 @@ object V2rayConfigUtil {
|
||||
}
|
||||
}
|
||||
|
||||
fun getCustomConfigServerOutbound(content: Context, guid: String): V2rayConfig.OutboundBean? {
|
||||
val jsonConfig = content.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
|
||||
if (TextUtils.isEmpty(jsonConfig)) {
|
||||
return null
|
||||
}
|
||||
val v2rayConfig = Gson().fromJson(jsonConfig, V2rayConfig::class.java) ?: return null
|
||||
for (outbound in v2rayConfig.outbounds) {
|
||||
if (outbound.protocol.equals(EConfigType.VMESS.name.toLowerCase()) ||
|
||||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) ||
|
||||
outbound.protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
|
||||
return outbound
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成v2ray的客户端配置文件
|
||||
*/
|
||||
@@ -116,6 +127,7 @@ object V2rayConfigUtil {
|
||||
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
|
||||
result.status = true
|
||||
result.content = jsonConfig
|
||||
parseDomainNameAndTag(app, jsonConfig)
|
||||
return result
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -164,8 +176,12 @@ object V2rayConfigUtil {
|
||||
try {
|
||||
val outbound = v2rayConfig.outbounds[0]
|
||||
|
||||
when (vmess.configType) {
|
||||
AppConfig.EConfigType.Vmess -> {
|
||||
val configType = EConfigType.fromInt(vmess.configType)
|
||||
if (configType != null) {
|
||||
outbound.protocol = configType.name.toLowerCase()
|
||||
}
|
||||
when (configType) {
|
||||
EConfigType.VMESS -> {
|
||||
outbound.settings?.servers = null
|
||||
|
||||
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
|
||||
@@ -183,10 +199,8 @@ object V2rayConfigUtil {
|
||||
|
||||
//远程服务器底层传输配置
|
||||
outbound.streamSettings = boundStreamSettings(vmess)
|
||||
|
||||
outbound.protocol = "vmess"
|
||||
}
|
||||
AppConfig.EConfigType.Shadowsocks -> {
|
||||
EConfigType.SHADOWSOCKS -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
@@ -199,10 +213,8 @@ object V2rayConfigUtil {
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
|
||||
outbound.protocol = "shadowsocks"
|
||||
}
|
||||
AppConfig.EConfigType.Socks -> {
|
||||
EConfigType.SOCKS -> {
|
||||
outbound.settings?.vnext = null
|
||||
|
||||
val server = outbound.settings?.servers?.get(0)
|
||||
@@ -211,21 +223,22 @@ object V2rayConfigUtil {
|
||||
|
||||
//Mux
|
||||
outbound.mux?.enabled = false
|
||||
|
||||
outbound.protocol = "socks"
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
|
||||
var serverDomain: String
|
||||
if(Utils.isIpv6Address(vmess.address)) {
|
||||
serverDomain = String.format("[%s]:%s", vmess.address, vmess.port)
|
||||
val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
|
||||
String.format("[%s]:%s", vmess.address, vmess.port)
|
||||
} else {
|
||||
serverDomain = String.format("%s:%s", vmess.address, vmess.port)
|
||||
String.format("%s:%s", vmess.address, vmess.port)
|
||||
}
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, serverDomain)
|
||||
|
||||
val tags = LinkedHashSet<String>()
|
||||
v2rayConfig.outbounds.forEach {
|
||||
if (!TextUtils.isEmpty(it.tag)) {
|
||||
tags.add(it.tag)
|
||||
}
|
||||
}
|
||||
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return false
|
||||
@@ -246,7 +259,7 @@ object V2rayConfigUtil {
|
||||
//streamSettings
|
||||
when (streamSettings.network) {
|
||||
"kcp" -> {
|
||||
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean()
|
||||
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
|
||||
kcpsettings.mtu = 1350
|
||||
kcpsettings.tti = 50
|
||||
kcpsettings.uplinkCapacity = 12
|
||||
@@ -254,34 +267,33 @@ object V2rayConfigUtil {
|
||||
kcpsettings.congestion = false
|
||||
kcpsettings.readBufferSize = 1
|
||||
kcpsettings.writeBufferSize = 1
|
||||
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean.HeaderBean()
|
||||
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
|
||||
kcpsettings.header.type = vmess.headerType
|
||||
streamSettings.kcpsettings = kcpsettings
|
||||
streamSettings.kcpSettings = kcpsettings
|
||||
}
|
||||
"ws" -> {
|
||||
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean()
|
||||
wssettings.connectionReuse = true
|
||||
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean()
|
||||
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean.HeadersBean()
|
||||
wssettings.headers.Host = host
|
||||
}
|
||||
if (!TextUtils.isEmpty(path)) {
|
||||
wssettings.path = path
|
||||
}
|
||||
streamSettings.wssettings = wssettings
|
||||
streamSettings.wsSettings = wssettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean()
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
if (!TextUtils.isEmpty(host)) {
|
||||
tlssettings.serverName = host
|
||||
}
|
||||
streamSettings.tlssettings = tlssettings
|
||||
streamSettings.tlsSettings = tlssettings
|
||||
}
|
||||
"h2" -> {
|
||||
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpsettingsBean()
|
||||
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
@@ -289,31 +301,30 @@ object V2rayConfigUtil {
|
||||
httpsettings.host = host.split(",").map { it.trim() }
|
||||
}
|
||||
httpsettings.path = path
|
||||
streamSettings.httpsettings = httpsettings
|
||||
streamSettings.httpSettings = httpsettings
|
||||
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean()
|
||||
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
|
||||
tlssettings.allowInsecure = true
|
||||
streamSettings.tlssettings = tlssettings
|
||||
streamSettings.tlsSettings = tlssettings
|
||||
}
|
||||
"quic" -> {
|
||||
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean()
|
||||
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
|
||||
val host = vmess.requestHost.trim()
|
||||
val path = vmess.path.trim()
|
||||
|
||||
quicsettings.security = host
|
||||
quicsettings.key = path
|
||||
|
||||
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean.HeaderBean()
|
||||
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean.HeaderBean()
|
||||
quicsettings.header.type = vmess.headerType
|
||||
|
||||
streamSettings.quicsettings = quicsettings
|
||||
streamSettings.quicSettings = quicsettings
|
||||
}
|
||||
else -> {
|
||||
//tcp带http伪装
|
||||
if (vmess.headerType == "http") {
|
||||
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean()
|
||||
tcpSettings.connectionReuse = true
|
||||
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean.HeaderBean()
|
||||
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
|
||||
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
|
||||
tcpSettings.header.type = vmess.headerType
|
||||
|
||||
// if (requestObj.has("headers")
|
||||
@@ -619,42 +630,58 @@ object V2rayConfigUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseDomainName(jsonConfig: String): String {
|
||||
private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
|
||||
try {
|
||||
val jObj = JSONObject(jsonConfig)
|
||||
var domainName: String
|
||||
var domainName = ""
|
||||
val tags = LinkedHashSet<String>()
|
||||
if (jObj.has("outbound")) {
|
||||
domainName = parseDomainName(jObj.optJSONObject("outbound"))
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
return domainName
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
|
||||
domainName = domain
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
if (jObj.has("outbounds")) {
|
||||
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
|
||||
domainName = parseDomainName(jObj.optJSONArray("outbounds").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
return domainName
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outbounds").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
|
||||
domainName = domain
|
||||
}
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jObj.has("outboundDetour")) {
|
||||
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
|
||||
domainName = parseDomainName(jObj.optJSONArray("outboundDetour").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
return domainName
|
||||
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outboundDetour").getJSONObject(i))
|
||||
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
|
||||
domainName = domain
|
||||
}
|
||||
if (!TextUtils.isEmpty(tag)) {
|
||||
tags.add(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!TextUtils.isEmpty(domainName)) {
|
||||
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
|
||||
}
|
||||
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun parseDomainName(outbound: JSONObject): String {
|
||||
private fun parseDomainNameAndTag(outbound: JSONObject): Pair<String, String> {
|
||||
val tag = if (outbound.has("tag")) {
|
||||
outbound.getString("tag")
|
||||
} else {
|
||||
""
|
||||
}
|
||||
try {
|
||||
if (outbound.has("settings")) {
|
||||
var vnext: JSONArray?
|
||||
val vnext: JSONArray?
|
||||
if (outbound.optJSONObject("settings").has("vnext")) {
|
||||
// vmess
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
|
||||
@@ -662,22 +689,22 @@ object V2rayConfigUtil {
|
||||
// shadowsocks or socks
|
||||
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
|
||||
} else {
|
||||
return ""
|
||||
return Pair("", tag)
|
||||
}
|
||||
for (i in 0..(vnext.length() - 1)) {
|
||||
val item = vnext.getJSONObject(i)
|
||||
val address = item.getString("address")
|
||||
val port = item.getString("port")
|
||||
if(Utils.isIpv6Address(address)) {
|
||||
return String.format("[%s]:%s", address, port)
|
||||
return Pair(String.format("[%s]:%s", address, port), tag)
|
||||
} else {
|
||||
return String.format("%s:%s", address, port)
|
||||
return Pair(String.format("%s:%s", address, port), tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return ""
|
||||
return Pair("", tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
V2rayNG/app/src/main/res/drawable/background_test_button.xml
Normal file
10
V2rayNG/app/src/main/res/drawable/background_test_button.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@color/secondary_text" />
|
||||
<item android:state_focused="true"
|
||||
android:drawable="@color/secondary_text" />
|
||||
<item android:state_hovered="true"
|
||||
android:drawable="@color/secondary_text" />
|
||||
<item android:drawable="@color/colorPrimary_text" />
|
||||
</selector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/accent"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#009963"/>
|
||||
<corners android:radius="20dp"/>
|
||||
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
|
||||
</shape>
|
||||
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_direct.png
Normal file
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_direct.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_proxy.png
Normal file
BIN
V2rayNG/app/src/main/res/drawable/ic_stat_proxy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -54,8 +54,10 @@
|
||||
android:id="@+id/layout_test"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/connection_test_height"
|
||||
android:background="@color/colorPrimary_text"
|
||||
android:gravity="center|left">
|
||||
android:background="@drawable/background_test_button"
|
||||
android:gravity="center|left"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_test_state"
|
||||
@@ -92,6 +94,8 @@
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:src="@drawable/ic_v_idle"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_anchorGravity="bottom|right|end" />
|
||||
|
||||
</com.github.jorgecastilloprz.FABProgressCircle>
|
||||
@@ -107,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>
|
||||
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
android:gravity="center_vertical"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/icon"
|
||||
@@ -40,6 +43,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingLeft="2dp"
|
||||
android:paddingEnd="6dp"
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="3dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
|
||||
<LinearLayout
|
||||
@@ -21,7 +17,11 @@
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@+id/layout_share">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -111,10 +111,14 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
||||
android:padding="@dimen/layout_margin_spacing"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@+id/info_container">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
@@ -126,10 +130,13 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
||||
android:padding="@dimen/layout_margin_spacing"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -141,10 +148,13 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_remove"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
||||
android:padding="@dimen/layout_margin_spacing"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/item_cardview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="3dp"
|
||||
@@ -13,7 +14,8 @@
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
card_view:cardCornerRadius="5dp">
|
||||
card_view:cardCornerRadius="5dp"
|
||||
android:nextFocusRight="@+id/layout_edit">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/info_container"
|
||||
@@ -62,11 +64,14 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_share"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height"
|
||||
android:visibility="invisible">
|
||||
android:padding="@dimen/layout_margin_spacing"
|
||||
android:visibility="invisible"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
@@ -78,10 +83,14 @@
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_edit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/server_height"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingEnd="@dimen/layout_margin_right_height">
|
||||
android:padding="@dimen/layout_margin_spacing"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:nextFocusLeft="@+id/item_cardview">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="@dimen/png_height"
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Button xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:onClick="onModeHelpClicked"
|
||||
android:text="@string/title_mode_help"
|
||||
android:textAlignment="textStart"
|
||||
android:textStyle="italic" />
|
||||
@@ -1,12 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -14,16 +7,9 @@
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/app_widget_name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
</LinearLayout>
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/ic_v" />
|
||||
</LinearLayout>
|
||||
@@ -4,12 +4,7 @@
|
||||
tools:showIn="navigation_view">
|
||||
|
||||
<group
|
||||
android:id="@+id/group_main"
|
||||
android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/server_profile"
|
||||
android:icon="@drawable/ic_description_white_24dp"
|
||||
android:title="@string/title_server" />
|
||||
android:id="@+id/group_main">
|
||||
<item
|
||||
android:id="@+id/sub_setting"
|
||||
android:icon="@drawable/ic_subscriptions_white_24dp"
|
||||
@@ -21,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>
|
||||
|
||||
@@ -6,4 +6,9 @@
|
||||
android:icon="@drawable/ic_copy_white"
|
||||
android:title="@string/logcat_copy"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/delete"
|
||||
android:icon="@drawable/ic_delete_white_24dp"
|
||||
android:title="@string/logcat_delete"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -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>
|
||||
@@ -83,7 +82,7 @@
|
||||
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">启用速度显示</string>
|
||||
<string name="summary_pref_speed_enabled">在通知中显示当前速度</string>
|
||||
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">启用流量探测</string>
|
||||
<string name="summary_pref_sniffing_enabled">流量探测</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>
|
||||
@@ -135,7 +135,8 @@
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">复制</string>
|
||||
<string name="title_export_all">导出全部配置至剪贴板</string>
|
||||
<string name="logcat_delete">删除</string>
|
||||
<string name="title_export_all">导出全部(非自定义)配置至剪贴板</string>
|
||||
<string name="title_sub_setting">订阅设置</string>
|
||||
<string name="sub_setting_remarks">备注</string>
|
||||
<string name="sub_setting_url">地址(url)</string>
|
||||
@@ -186,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>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<string name="toast_services_failure">啟動服務失敗</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">組態</string>
|
||||
<string name="title_server">設定檔</string>
|
||||
<string name="menu_item_add_config">新增組態</string>
|
||||
<string name="menu_item_save_config">儲存組態</string>
|
||||
<string name="menu_item_del_config">刪除組態</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="title_file_chooser">選取一個設定檔</string>
|
||||
<string name="toast_require_file_manager">請安裝檔案總管。</string>
|
||||
<string name="server_customize_config">自訂組態</string>
|
||||
<string name="toast_config_file_invalid">無效組態</string>
|
||||
<string name="toast_config_file_invalid">無效設定檔</string>
|
||||
<string name="server_lab_content">內容</string>
|
||||
<string name="toast_none_data_clipboard">剪貼簿內無資料</string>
|
||||
<string name="toast_invalid_url">網址無效</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>
|
||||
@@ -84,7 +83,7 @@
|
||||
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">啟用速度顯示</string>
|
||||
<string name="summary_pref_speed_enabled">在通知中顯示當前速度</string>
|
||||
<string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖標顯示流量的路由情況</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">啟用流量探測</string>
|
||||
<string name="summary_pref_sniffing_enabled">流量探測</string>
|
||||
@@ -104,17 +103,17 @@
|
||||
<string name="summary_pref_donate">向開發人員捐款</string>
|
||||
<string name="donate_detail">新增一些實驗性進階功能</string>
|
||||
|
||||
<string name="title_pref_remote_dns">遠端DNS (可选)</string>
|
||||
<string name="title_pref_remote_dns">遠端DNS (可選)</string>
|
||||
<string name="summary_pref_remote_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_domestic_dns">境内DNS (可选,僅本地DNS模式下生效)</string>
|
||||
<string name="title_pref_domestic_dns">境内DNS (可選,僅本地DNS模式下生效)</string>
|
||||
<string name="summary_pref_domestic_dns">DNS</string>
|
||||
|
||||
<string name="title_pref_socks_port">SOCKS5代理端口</string>
|
||||
<string name="summary_pref_socks_port">SOCKS5代理端口</string>
|
||||
<string name="title_pref_socks_port">SOCKS5代理連接埠</string>
|
||||
<string name="summary_pref_socks_port">SOCKS5代理連接埠</string>
|
||||
|
||||
<string name="title_pref_http_port">HTTP代理端口</string>
|
||||
<string name="summary_pref_http_port">HTTP代理端口</string>
|
||||
<string name="title_pref_http_port">HTTP代理連接埠</string>
|
||||
<string name="summary_pref_http_port">HTTP代理連接埠</string>
|
||||
|
||||
|
||||
<string name="title_pref_feedback">回饋</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>
|
||||
@@ -137,7 +137,8 @@
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">複製</string>
|
||||
<string name="title_export_all">匯出全部配置至剪貼簿</string>
|
||||
<string name="logcat_delete">刪除</string>
|
||||
<string name="title_export_all">匯出全部(非自訂)配置至剪貼簿</string>
|
||||
<string name="title_sub_setting">訂閱設定</string>
|
||||
<string name="sub_setting_remarks">備註</string>
|
||||
<string name="sub_setting_url">位址(url)</string>
|
||||
@@ -153,7 +154,7 @@
|
||||
<string name="routing_settings_delete">清除</string>
|
||||
<string name="routing_settings_scan_replace">掃描並取代</string>
|
||||
<string name="routing_settings_scan_append">掃描並附加</string>
|
||||
<string name="routing_settings_default_rules">設置默認路由規則</string>
|
||||
<string name="routing_settings_default_rules">設置預設路由規則</string>
|
||||
|
||||
<string name="connection_test_pending">"檢查連線能力"</string>
|
||||
<string name="connection_test_testing">"測試中……"</string>
|
||||
@@ -178,13 +179,19 @@
|
||||
|
||||
<string-array name="routing_mode">
|
||||
<item>全球</item>
|
||||
<item>略過局域網</item>
|
||||
<item>略過區域網路</item>
|
||||
<item>略過中國大陸</item>
|
||||
<item>略過局域網及中國大陸</item>
|
||||
<item>略過區域網路及中國大陸</item>
|
||||
</string-array>
|
||||
<string name="title_pref_proxy_sharing_enabled">代理共享</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">綁定代理入口ip到0.0.0.0</string>
|
||||
<string name="toast_warning_pref_proxysharing">其他設備可以使用socks/http協議通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網絡中啟用以避免未經授權的連接</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網絡</string>
|
||||
<string name="toast_warning_pref_proxysharing">其他設備可以使用socks/http協定通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網路中啟用以避免未經授權的連接</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string>
|
||||
<string name="toast_malformed_josn">配置格式錯誤</string>
|
||||
|
||||
<string-array name="mode_entries">
|
||||
<item>VPN</item>
|
||||
<item>僅代理</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -66,4 +66,44 @@
|
||||
<item>IPIfNonMatch</item>
|
||||
<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>
|
||||
<item>8.0.0.0/7</item>
|
||||
<item>11.0.0.0/8</item>
|
||||
<item>12.0.0.0/6</item>
|
||||
<item>16.0.0.0/4</item>
|
||||
<item>32.0.0.0/3</item>
|
||||
<item>64.0.0.0/2</item>
|
||||
<item>128.0.0.0/3</item>
|
||||
<item>160.0.0.0/5</item>
|
||||
<item>168.0.0.0/6</item>
|
||||
<item>172.0.0.0/12</item>
|
||||
<item>172.32.0.0/11</item>
|
||||
<item>172.64.0.0/10</item>
|
||||
<item>172.128.0.0/9</item>
|
||||
<item>173.0.0.0/8</item>
|
||||
<item>174.0.0.0/7</item>
|
||||
<item>176.0.0.0/4</item>
|
||||
<item>192.0.0.0/9</item>
|
||||
<item>192.128.0.0/11</item>
|
||||
<item>192.160.0.0/13</item>
|
||||
<item>192.169.0.0/16</item>
|
||||
<item>192.170.0.0/15</item>
|
||||
<item>192.172.0.0/14</item>
|
||||
<item>192.176.0.0/12</item>
|
||||
<item>192.192.0.0/10</item>
|
||||
<item>193.0.0.0/8</item>
|
||||
<item>194.0.0.0/7</item>
|
||||
<item>196.0.0.0/6</item>
|
||||
<item>200.0.0.0/5</item>
|
||||
<item>208.0.0.0/4</item>
|
||||
<item>224.0.0.0/3</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<dimen name="bypass_list_header_height">50dp</dimen>
|
||||
<dimen name="layout_margin_top_height">16dp</dimen>
|
||||
<dimen name="layout_margin_right_height">16dp</dimen>
|
||||
<dimen name="layout_margin_spacing">8dp</dimen>
|
||||
<dimen name="edit_height">50dp</dimen>
|
||||
<dimen name="png_height">24dp</dimen>
|
||||
<dimen name="server_height">72dp</dimen>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
<string name="server_lab_network">network</string>
|
||||
<string name="server_lab_more_function">more function</string>
|
||||
<string name="server_lab_head_type">head type</string>
|
||||
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC securty</string>
|
||||
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string>
|
||||
<string name="server_lab_path">path(ws path/h2 path)/QUIC key</string>
|
||||
<string name="server_lab_stream_security">tls</string>
|
||||
<string name="server_lab_allow_insecure">allowInsecure</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>
|
||||
@@ -84,7 +83,8 @@
|
||||
<string name="summary_pref_mux_enabled">Enable maybe speed up network and switch network maybe flash</string>
|
||||
|
||||
<string name="title_pref_speed_enabled">Enable speed display</string>
|
||||
<string name="summary_pref_speed_enabled">Display current speed in the notification</string>
|
||||
<string name="summary_pref_speed_enabled">Display current speed in the notification.\nNotification icon would change based on
|
||||
usage.</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">Enable Sniffing</string>
|
||||
<string name="summary_pref_sniffing_enabled">Sniffing</string>
|
||||
@@ -124,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>
|
||||
@@ -136,7 +137,8 @@
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">Copy</string>
|
||||
<string name="title_export_all">Export all config to clipboard</string>
|
||||
<string name="logcat_delete">Delete</string>
|
||||
<string name="title_export_all">Export non-custom configs to clipboard</string>
|
||||
<string name="title_sub_setting">Subscription setting</string>
|
||||
<string name="sub_setting_remarks">remarks</string>
|
||||
<string name="sub_setting_url">url</string>
|
||||
@@ -187,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>
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar.Translucent">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
@@ -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.0.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
buildToolsVersion '28.0.3'
|
||||
compileSdkVersion Integer.parseInt("$compileSdkVer")
|
||||
buildToolsVersion buildToolsVer
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 17
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package me.dozen.dpreference;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -45,10 +47,6 @@ public class DPreference {
|
||||
PrefAccessor.setInt(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public void setPrefStringSet(final String key, final Set<String> value) {
|
||||
PrefAccessor.setStringSet(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public int getPrefInt(final String key, final int defaultValue) {
|
||||
return PrefAccessor.getInt(mContext, mName, key, defaultValue);
|
||||
}
|
||||
@@ -61,10 +59,26 @@ public class DPreference {
|
||||
return PrefAccessor.getLong(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefStringSet(final String key, final Set<String> value) {
|
||||
PrefAccessor.setStringSet(mContext, mName, key, value);
|
||||
}
|
||||
|
||||
public Set<String> getPrefStringSet(final String key, final Set<String> defaultValue) {
|
||||
return PrefAccessor.getStringSet(mContext, mName, key, defaultValue);
|
||||
}
|
||||
|
||||
public void setPrefStringOrderedSet(final String key, final LinkedHashSet<String> value) {
|
||||
PrefAccessor.setString(mContext, mName, key, StringSetConverter.encode(value));
|
||||
}
|
||||
|
||||
public LinkedHashSet<String> getPrefStringOrderedSet(final String key, final LinkedHashSet<String> defaultValue) {
|
||||
String value = PrefAccessor.getString(mContext, mName, key, "");
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return StringSetConverter.decode(value);
|
||||
}
|
||||
|
||||
public void removePreference(final String key) {
|
||||
PrefAccessor.remove(mContext, mName, key);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class StringSetConverter {
|
||||
@@ -13,8 +14,8 @@ public class StringSetConverter {
|
||||
return gson.toJson(src);
|
||||
}
|
||||
|
||||
public static Set<String> decode(String json) {
|
||||
Type setType = new TypeToken<Set<String>>() {
|
||||
public static LinkedHashSet<String> decode(String json) {
|
||||
Type setType = new TypeToken<LinkedHashSet<String>>() {
|
||||
}.getType();
|
||||
return gson.fromJson(json, setType);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
|
||||
# org.gradle.parallel=true
|
||||
#Fri Jun 02 14:08:42 CST 2017
|
||||
ankoVersion=0.10.8
|
||||
kotlinVersion=1.3.40
|
||||
kotlinVersion=1.4.0
|
||||
supportLibVersion=28.0.0
|
||||
buildToolsVer=28.0.3
|
||||
compileSdkVer=28
|
||||
buildToolsVer=29.0.3
|
||||
compileSdkVer=29
|
||||
kotlin.incremental=true
|
||||
targetSdkVer=28
|
||||
targetSdkVer=29
|
||||
|
||||
6
V2rayNG/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
V2rayNG/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Tue Feb 25 12:40:41 CST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
|
||||
Reference in New Issue
Block a user