Compare commits

..

65 Commits

Author SHA1 Message Date
2dust 870950e807 Merge pull request #577 from yuhan6665/fix-target-29
Fix release build with tun2socks
2020-08-30 07:21:24 +08:00
yuhan6665 e798cb3f42 Fix release build with tun2socks 2020-08-29 12:34:54 -04:00
2dust b970f0bcff Merge pull request #574 from yuhan6665/all-outbounds-speed
All outbounds speed
2020-08-29 20:33:18 +08:00
2dust 93b9a56428 Merge pull request #387 from yuhan6665/target-29
Target sdk 29
2020-08-29 20:32:59 +08:00
yuhan6665 eb8bd13266 Add speed notification for all outbounds 2020-08-28 21:52:44 -04:00
yuhan6665 d850c88c63 Support ordered set in DPreference
Note the ordered set need to be stored internally as String,
not string set
2020-08-28 21:52:43 -04:00
yuhan6665 6bee795c0d Update target sdk to 29 2020-08-27 20:19:00 -04:00
yuhan6665 d7d7e029e0 Fix compile warnings that will be error in sdk 29 2020-08-27 20:18:59 -04:00
2dust 8efdab43d7 Merge pull request #566 from yuhan6665/array-crash
Fix test crash if node is removed
2020-08-22 08:06:36 +08:00
yuhan6665 5e0235cf70 Fix test crash if node is removed
This should fix almost all issues when the testing is in progress
and the array is changed. However, there might be uncovered edge
cases since the vmess array is in both process and accessed by
multiple threads.
2020-08-21 19:47:33 -04:00
2dust 9f668b3da7 Merge pull request #559 from yuhan6665/custom-config-improve
Custom config improve
2020-08-16 20:26:17 +08:00
yuhan6665 b2437279dc Update UI for custom config
- add server address and port
- add share button (full configuration)
- update "Export all config to clipboard" text to indicate
it will not include custom config
2020-08-15 19:37:59 -04:00
yuhan6665 180b5efd93 Enable tcping test for custom config 2020-08-15 19:37:59 -04:00
yuhan6665 dc31380cc2 Refactor EConfigType to be an enum
Using enum will simplify logic and conveniently providing Int value
and a String name
2020-08-15 19:37:59 -04:00
2dust 1361e0dacf Merge pull request #555 from yuhan6665/per-app-list
Update latest per-app list
2020-08-15 20:40:10 +08:00
2dust a45eb66bd1 Merge pull request #554 from ShrdBB/master
Fix typo in strings.xml
2020-08-15 20:39:50 +08:00
yuhan6665 508102cebe Update latest per-app list
https://github.com/2dust/androidpackagenamelist/blob/master/proxy.txt
2020-08-14 20:45:30 -04:00
ShrdBB 1d86dbb9f3 fix typo in strings.xml 2020-08-14 12:48:46 +08:00
2dust 20ca554be2 Merge pull request #544 from yuhan6665/android-system
Add "Android System" in per-app vpn
2020-08-09 12:57:49 +08:00
yuhan6665 25ba455656 Add "Android System" in per-app vpn 2020-08-08 23:49:06 -04:00
2dust 17e7c62d53 Merge pull request #542 from yuhan6665/master
Sanitize project configuration
2020-08-08 15:35:16 +08:00
yuhan6665 6f0f2fdeda Sanitize project configuration
Make project compilable out of box. This commit address
the following:
- remove release key
- fix gitignore for Android Studio temp files
- add gradle wrapper properties
2020-08-07 21:37:00 -04:00
2dust 3c9c9b5a4c Merge pull request #531 from yuhan6665/emui-notification
Fix notification for Emui 4.1
2020-08-06 20:40:45 +08:00
2dust e13024d6bb Merge pull request #526 from Vixb1122/master
exclude ScSwitchActivity from recent list
2020-08-06 20:39:08 +08:00
2dust 9d58edd31f Merge pull request #537 from yuhan6665/revert
Revert "Refresh prepared domain every 30 minutes"
2020-08-06 20:38:50 +08:00
yuhan6665 af7dfc3a43 Revert "Refresh prepared domain every 30 minutes"
This reverts commit 903352ec9c.
2020-08-05 20:22:01 -04:00
yuhan6665 ca554e6ac4 Fix notification for Emui 4.1 2020-08-02 08:42:04 -04:00
xuezhixin ec391d8689 exclude ScSwitchActivity from recent list 2020-07-29 16:47:40 +08:00
2dust 6afd4d0549 Update AngConfigManager.kt 2020-07-27 21:00:34 +08:00
2dust 957cf85362 Merge pull request #517 from yuhan6665/bypass-private
Bypass private IP at VPN service
2020-07-25 19:42:12 +08:00
2dust 881152e10a Merge pull request #516 from cpdyj/master
add support to new version vmess link
2020-07-25 19:41:46 +08:00
2dust 6e47ebb27a Merge pull request #484 from DevRyz3n/drag-item-store-config
Optimize invoke timing of store config file after dragging item.
2020-07-25 19:41:13 +08:00
2dust a731e6c360 Merge pull request #453 from yuhan6665/widget
Widget
2020-07-25 19:40:39 +08:00
yuhan6665 d1466ba4b2 Bypass private IP at VPN service 2020-07-24 18:12:20 -04:00
iseki 617f28f399 Update AngConfigManager.kt 2020-07-24 11:23:46 +08:00
iseki c0ec0d9404 Update AngConfigManager.kt 2020-07-24 11:21:20 +08:00
iseki 3419dc8837 add support to new version vmess link
some information can be found at: https://github.com/v2ray/discussion/issues/720
2020-07-23 00:51:08 +08:00
2dust 786aaf823a Merge pull request #511 from yuhan6665/outbound-speed
Outbound speed
2020-07-19 08:19:03 +08:00
yuhan6665 1144183da4 Update notification icon based on traffic
Notification icon would be a good indication for current traffic
- going through proxy
- connect directly
- not going through v2rayNG
2020-07-18 18:48:47 -04:00
yuhan6665 5bf2fda990 Measure traffic from outbounds
With v2ray-core 4.26.0, traffic can be measured from outbounds.
Stats is shown separately for proxy and direct traffic.

In the future, it is possible to add stats for each server node and
even historic usage graph.
2020-07-18 18:47:59 -04:00
2dust 28639cc388 Merge pull request #468 from yuhan6665/cleanup-config
Remove legacy "connectionReuse" in config
2020-07-18 14:01:38 +08:00
2dust ca254b2aa1 Merge pull request #443 from yuhan6665/resume-speed
Fix speed display after screen turns on
2020-07-18 14:00:48 +08:00
2dust 9721879713 Merge pull request #432 from yuhan6665/drawer-ui
fix highlight issue in drawer ui
2020-07-18 13:59:47 +08:00
2dust 623c1807c5 Merge pull request #409 from yuhan6665/test-cleanup
Tcp test cleanup
2020-07-18 13:58:37 +08:00
2dust 739fa88ba7 Merge pull request #335 from yuhan6665/sub-index
Fix selected index when update subscription
2020-07-18 13:58:11 +08:00
2dust 85d6f00f8c Merge pull request #474 from yuhan6665/dns-cache-timeout
Refresh prepared domain every 30 minutes
2020-07-18 09:48:16 +08:00
yuhan6665 8986710453 Cancel async block when user test tcping again
Change async code to use Kotlin coroutine instead of Anko,
since Anko is deprecated and coroutine is the recommanded
approach now.
2020-07-16 19:42:43 -04:00
yuhan6665 f54faacbf6 Close connecting sockets when user test tcping again 2020-07-16 19:42:43 -04:00
r23 993ee0b8d2 Optimize invoke timing of store config file after dragging item. 2020-06-29 01:24:19 +08:00
yuhan6665 903352ec9c Refresh prepared domain every 30 minutes 2020-06-19 23:42:02 -04:00
yuhan6665 ad56106c08 Remove legacy "connectionReuse" in config
Also auto-refactor the data class naming to be proper camelcase
2020-06-13 23:38:33 -04:00
yuhan6665 91b8284afd Improve widget UI
Change widget color when clicked
2020-06-06 00:18:19 -04:00
yuhan6665 ef9e0cc0d2 Add back widget, fix implicit Intent 2020-06-06 00:18:19 -04:00
2dust 6577c46a31 Merge pull request #425 from yuhan6665/translation
Fix a traditional Chinese translation
2020-05-31 16:13:35 +08:00
yuhan6665 c6dab001b2 Fix traditional Chinese translation 2020-05-30 12:34:29 -04:00
yuhan6665 aea8369b8a Fix speed display after screen turns on
Also, use the tab to make the text less jumpy
2020-05-29 22:54:55 -04:00
yuhan6665 92d2cb35c4 Make drawer item unchecked
Current checked item is not consistent with the active activity.
In fact, we don't need checked state. This is a standard behavior.
You can find in apps like Google Playstore.
2020-05-24 11:13:12 -04:00
yuhan6665 6ce3d540e8 Remove config item in drawer
The drawer is attached to MainActivity. When user see the drawer, the
MainActivity must be active. Showing an menu item to launch itself is
confusing.
2020-05-24 11:09:11 -04:00
2dust c105d84b35 Merge pull request #300 from yuhan6665/master
Fix UI touch issues for Android TV
2020-04-12 10:04:52 +08:00
yuhan6665 8b149fb52f Fix selected index when update subscription 2020-04-03 18:58:41 -04:00
2dust 3ea04c076c Merge pull request #314 from yuhan6665/chrome-os
Add tags in AndroidManifest to support PlayStore filtering
2020-03-16 16:24:58 +08:00
2dust 98475460bf Merge pull request #313 from yuhan6665/lint-fix
Use annotation to fix lint error
2020-03-16 16:24:08 +08:00
yuhan6665 68ee61a753 Add tags in AndroidManifest to support PlayStore filtering
Google Playstore is using uses-feature and supports-screens
to filter devices.
By setting all screens to true and specifing features as
"not required" should make app compatible with more devices
Particularly it includes Chrome OS devices.
2020-03-14 14:07:31 -04:00
yuhan6665 90ba9ef2b7 Use annotation to fix lint error 2020-03-14 13:03:51 -04:00
yuhan6665 0ec114322e Fix UI touch issues for Android TV 2020-03-08 12:06:44 -04:00
50 changed files with 856 additions and 487 deletions
+3 -5
View File
@@ -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/geoip.dat
V2rayNG/app/src/main/assets/geosite.dat V2rayNG/app/src/main/assets/geosite.dat
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
*.dat *.dat
*.jks *.jks
V2rayNG/gradle/wrapper/gradle-wrapper.properties V2rayNG/app/release/output.json
V2rayNG/gradle/wrapper/gradle-wrapper.properties .idea/
V2rayNG/app/release/output.json .gradle/
+5 -4
View File
@@ -5,14 +5,15 @@ import (
) )
type Status struct { type Status struct {
IsRunning bool IsRunning bool
PackageName string PackageName string
PackageCodePath string
Vpoint v2core.Server Vpoint v2core.Server
} }
func CheckVersion() int { func CheckVersion() int {
return 20 return 21
} }
func (v *Status) GetDataDir() string { func (v *Status) GetDataDir() string {
@@ -20,7 +21,7 @@ func (v *Status) GetDataDir() string {
} }
func (v *Status) GetApp(name string) 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) { func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {
+4 -2
View File
@@ -44,6 +44,7 @@ type V2RayPoint struct {
closeChan chan struct{} closeChan chan struct{}
PackageName string PackageName string
PackageCodePath string
DomainName string DomainName string
ConfigureFileContent string ConfigureFileContent string
EnableLocalDNS bool EnableLocalDNS bool
@@ -67,6 +68,7 @@ func (v *V2RayPoint) RunLoop() (err error) {
defer v.v2rayOP.Unlock() defer v.v2rayOP.Unlock()
//Construct Context //Construct Context
v.status.PackageName = v.PackageName v.status.PackageName = v.PackageName
v.status.PackageCodePath = v.PackageCodePath
if !v.status.IsRunning { if !v.status.IsRunning {
v.closeChan = make(chan struct{}) v.closeChan = make(chan struct{})
@@ -116,7 +118,7 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
if v.statsManager == nil { if v.statsManager == nil {
return 0 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 { if counter == nil {
return 0 return 0
} }
@@ -233,7 +235,7 @@ func (v V2RayPoint) runTun2socks() error {
v.escorter.EscortingUp() v.escorter.EscortingUp()
go v.escorter.EscortRun( go v.escorter.EscortRun(
v.status.GetApp("tun2socks"), v.status.GetApp("libtun2socks.so"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "", v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
v.SupportSet.SendFd) v.SupportSet.SendFd)
+7 -20
View File
@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
android { android {
compileSdkVersion 28 compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion '28.0.3' buildToolsVersion buildToolsVer
compileOptions { compileOptions {
targetCompatibility = "8" targetCompatibility = "8"
@@ -20,34 +20,17 @@ android {
versionName "1.0.2" 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 { buildTypes {
release { release {
minifyEnabled false minifyEnabled false
zipAlignEnabled false zipAlignEnabled false
shrinkResources false shrinkResources false
signingConfig signingConfigs.release
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
debug { debug {
minifyEnabled false minifyEnabled false
zipAlignEnabled false zipAlignEnabled false
shrinkResources false shrinkResources false
signingConfig signingConfigs.release
} }
} }
@@ -84,6 +67,10 @@ dependencies {
implementation project(':dpreference') implementation project(':dpreference')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.2" // 1.3.x has compile error:
// More than one file was found with OS independent path 'META-INF/proguard/coroutines.pro'
// Android support library // Android support library
implementation "com.android.support:support-v4:$supportLibVersion" implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion" implementation "com.android.support:appcompat-v7:$supportLibVersion"
@@ -127,4 +114,4 @@ repositories {
flatDir { flatDir {
dirs 'libs' dirs 'libs'
} }
} }
Binary file not shown.
+25 -12
View File
@@ -2,6 +2,16 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.v2ray.ang"> 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.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@@ -66,7 +76,10 @@
<activity android:name=".ui.SubEditActivity" /> <activity android:name=".ui.SubEditActivity" />
<activity android:name=".ui.ScScannerActivity" /> <activity android:name=".ui.ScScannerActivity" />
<activity android:name=".ui.ScSwitchActivity" /> <activity
android:name=".ui.ScSwitchActivity"
android:excludeFromRecents="true"
android:theme="@style/AppTheme.NoActionBar.Translucent" />
<service <service
android:name=".service.V2RayVpnService" android:name=".service.V2RayVpnService"
@@ -83,16 +96,16 @@
android:value="true" /> android:value="true" />
</service> </service>
<!--<receiver android:name=".receiver.WidgetProvider">--> <receiver android:name=".receiver.WidgetProvider"
<!--<meta-data--> android:process=":RunSoLibV2RayDaemon">
<!--android:name="android.appwidget.provider"--> <meta-data
<!--android:resource="@xml/app_widget_provider" />--> android:name="android.appwidget.provider"
android:resource="@xml/app_widget_provider" />
<!--<intent-filter>--> <intent-filter>
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />--> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<!--<action android:name="com.v2ray.ang.action.widget.click" />--> <action android:name="com.v2ray.ang.action.widget.click" />
<!--</intent-filter>--> </intent-filter>
<!--</receiver>--> </receiver>
<service <service
android:name=".service.QSTileService" android:name=".service.QSTileService"
@@ -122,4 +135,4 @@
</application> </application>
</manifest> </manifest>
+166 -121
View File
@@ -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 amanita_design.samorost3.gp
com.devolver.reigns2 android
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
au.com.shiftyjelly.pocketcasts au.com.shiftyjelly.pocketcasts
com.google.android.gms bbc.mobile.news.ww
com.android.providers.telephony
com.resilio.sync
com.google.android.apps.googlevoice
com.discord
com.cradle.iitc_mobile
be.mygod.vpnhotspot be.mygod.vpnhotspot
ch.protonmail.android
co.wanqu.android
com.alphainventor.filemanager com.alphainventor.filemanager
com.amazon.kindle
com.amazon.mshop.android.shopping
com.android.chrome
com.android.providers.downloads 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.apkpure.aegon
com.apkupdater
com.app.pornhub
com.arthurivanets.owly
com.asahi.tida.tablet
com.authy.authy
com.avmovie
com.ballistiq.artstation com.ballistiq.artstation
com.binance.dev
com.bitly.app 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.canary
com.chrome.dev com.chrome.dev
com.cl.newt66y
com.cradle.iitc_mobile
com.cygames.shadowverse
com.devhd.feedly com.devhd.feedly
com.devolver.reigns2
com.discord
com.downloader.video.tumblr
com.driverbrowser
com.dropbox.android com.dropbox.android
com.duolingo
com.duckduckgo.mobile.android
com.dv.adm
com.estrongs.android.pop com.estrongs.android.pop
com.estrongs.android.pop.pro 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.fastaccess.github
com.felixfilip.scpae
com.fireproofstudios.theroom4
com.firstrowria.pushnotificationtester com.firstrowria.pushnotificationtester
com.flyersoft.moonreaderp
com.fooview.android.fooview
com.fvd.eversync com.fvd.eversync
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.gianlu.aria2app com.gianlu.aria2app
com.github.yeriomin.yalpstore 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.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.instantapps.supervisor
com.google.android.keep
com.google.android.music
com.google.android.ogyoutube com.google.android.ogyoutube
com.google.android.partnersetup com.google.android.partnersetup
com.google.android.play.games
com.google.android.street
com.google.android.syncadapters.calendar com.google.android.syncadapters.calendar
com.google.android.syncadapters.contacts com.google.android.syncadapters.contacts
com.google.android.talk
com.google.android.tts com.google.android.tts
com.google.android.videos
com.google.android.youtube
com.google.ar.lens
com.hochan.coldsoup com.hochan.coldsoup
com.ifttt.ifttt com.ifttt.ifttt
com.imgur.mobile com.imgur.mobile
com.innologica.inoreader com.innologica.inoreader
com.instagram.android
com.instapaper.android com.instapaper.android
com.jarvanh.vpntether com.jarvanh.vpntether
com.kapp.youtube.final
com.klinker.android.twitter_l
com.lastpass.lpandroid
com.linecorp.linelite
com.lingodeer
com.mediapods.tumbpods com.mediapods.tumbpods
com.mgoogle.android.gms com.mgoogle.android.gms
com.microsoft.emmx
com.microsoft.office.powerpoint com.microsoft.office.powerpoint
com.microsoft.skydrive
com.mixplorer com.mixplorer
com.msd.consumerchinese com.msd.consumerchinese
com.msd.professionalchinese com.msd.professionalchinese
com.mss2011c.sharehelper com.mss2011c.sharehelper
com.netflix.mediaclient
com.newin.nplayer.pro 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.oasisfeng.island
com.ocnt.liveapp.hw
com.orekie.search com.orekie.search
com.patreon.android
com.paypal.android.p2pmobile
com.perol.asdpl.pixivez
com.pinterest
com.popularapp.periodcalendar
com.popularapp.videodownloaderforinstagram com.popularapp.videodownloaderforinstagram
com.pushbullet.android 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.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.slack
com.snaptube.premium
com.sololearn
com.sonelli.juicessh
com.spotify.music
com.tencent.huatuo com.tencent.huatuo
com.termux com.termux
com.teslacoilsw.launcher
com.theinitium.news
com.thomsonreuters.reuters
com.thunkable.android.hritvik00.freenom com.thunkable.android.hritvik00.freenom
com.topjohnwu.magisk com.topjohnwu.magisk
com.tripadvisor.tripadvisor
com.tumblr
com.twitter.android
com.u91porn com.u91porn
com.u9porn 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.vimeo.android.videoapp
com.vivaldi.browser
com.vivaldi.browser.snapshot
com.vkontakte.android
com.whatsapp
com.wire
com.wuxiangai.refactor com.wuxiangai.refactor
com.xda.labs
com.xvideos.app
com.yandex.browser com.yandex.browser
com.yandex.browser.beta
com.yandex.browser.alpha
com.z28j.feel com.z28j.feel
con.medium.reader
de.apkgrabber
de.robv.android.xposed.installer de.robv.android.xposed.installer
dk.tacit.android.foldersync.full dk.tacit.android.foldersync.full
es.rafalense.telegram.themes es.rafalense.telegram.themes
es.rafalense.themes es.rafalense.themes
flipboard.app flipboard.app
fm.moon.app
fr.gouv.etalab.mastodon
github.tornaco.xposedmoduletest github.tornaco.xposedmoduletest
idm.internet.download.manager
idm.internet.download.manager.plus
io.github.javiewer
io.github.skyhacker2.magnetsearch
io.va.exposed io.va.exposed
it.mvilla.android.fenix2
jp.bokete.app.android
jp.naver.line.android
jp.pxv.android jp.pxv.android
luo.speedometergpspro
mark.via.gp
me.tshine.easymark me.tshine.easymark
net.teeha.android.url_shortener net.teeha.android.url_shortener
net.tsapps.appsales
onion.fire onion.fire
org.fdroid.fdroid org.fdroid.fdroid
org.freedownloadmanager.fdm
org.kustom.widget
org.mozilla.fennec_aurora 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.schabi.newpipe
org.telegram.messenger org.telegram.messenger
org.telegram.multi
org.telegram.plus
org.thunderdog.challegram
org.torproject.android org.torproject.android
org.torproject.torbrowser_alpha
org.wikipedia
org.xbmc.kodi org.xbmc.kodi
pl.zdunex25.updater 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 tv.twitch.android.app
com.shanga.walli tw.com.gamer.android.activecenter
com.whatsapp videodownloader.downloadvideo.downloader
com.wire uk.co.bbc.learningenglish
com.simplehabit.simplehabitapp com.ted.android
@@ -13,8 +13,8 @@
} }
}, },
"system": { "system": {
"statsInboundUplink": true, "statsOutboundUplink": true,
"statsInboundDownlink": true "statsOutboundDownlink": true
} }
}, },
"inbounds": [{ "inbounds": [{
@@ -43,6 +43,8 @@ public interface ItemTouchHelperAdapter {
boolean onItemMove(int fromPosition, int toPosition); boolean onItemMove(int fromPosition, int toPosition);
void onItemMoveCompleted();
/** /**
* Called when an item has been dismissed by a swipe.<br/> * Called when an item has been dismissed by a swipe.<br/>
* <br/> * <br/>
@@ -112,6 +112,8 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder); super.clearView(recyclerView, viewHolder);
mAdapter.onItemMoveCompleted();
viewHolder.itemView.setAlpha(ALPHA_FULL); viewHolder.itemView.setAlpha(ALPHA_FULL);
if (viewHolder instanceof ItemTouchHelperViewHolder) { if (viewHolder instanceof ItemTouchHelperViewHolder) {
Binary file not shown.
Binary file not shown.
@@ -11,6 +11,7 @@ object AppConfig {
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid" 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_NAME = "pref_v2ray_config_name"
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain" 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_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val VMESS_PROTOCOL: String = "vmess://" const val VMESS_PROTOCOL: String = "vmess://"
const val SS_PROTOCOL: String = "ss://" const val SS_PROTOCOL: String = "ss://"
@@ -50,12 +51,4 @@ object AppConfig {
const val MSG_STATE_STOP = 4 const val MSG_STATE_STOP = 4
const val MSG_STATE_STOP_SUCCESS = 41 const val MSG_STATE_STOP_SUCCESS = 41
const val MSG_STATE_RESTART = 5 const val MSG_STATE_RESTART = 5
}
object EConfigType {
val Vmess = 1
val Custom = 2
val Shadowsocks = 3
val Socks = 4
}
}
@@ -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, data class StreamSettingsBean(var network: String,
var security: String, var security: String,
var tcpSettings: TcpsettingsBean?, var tcpSettings: TcpSettingsBean?,
var kcpsettings: KcpsettingsBean?, var kcpSettings: KcpSettingsBean?,
var wssettings: WssettingsBean?, var wsSettings: WsSettingsBean?,
var httpsettings: HttpsettingsBean?, var httpSettings: HttpSettingsBean?,
var tlssettings: TlssettingsBean?, var tlsSettings: TlsSettingsBean?,
var quicsettings: QuicsettingBean? var quicSettings: QuicSettingBean?
) { ) {
data class TcpsettingsBean(var connectionReuse: Boolean = true, data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) {
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none", data class HeaderBean(var type: String = "none",
var request: Any? = null, var request: Any? = null,
var response: 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 tti: Int = 20,
var uplinkCapacity: Int = 12, var uplinkCapacity: Int = 12,
var downlinkCapacity: Int = 100, var downlinkCapacity: Int = 100,
@@ -90,25 +89,43 @@ data class V2rayConfig(
data class HeaderBean(var type: String = "none") data class HeaderBean(var type: String = "none")
} }
data class WssettingsBean(var connectionReuse: Boolean = true, data class WsSettingsBean(var path: String = "",
var path: String = "",
var headers: HeadersBean = HeadersBean()) { var headers: HeadersBean = HeadersBean()) {
data class HeadersBean(var Host: String = "") 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 = "") var serverName: String = "")
data class QuicsettingBean(var security: String = "none", data class QuicSettingBean(var security: String = "none",
var key: String = "", var key: String = "",
var header: HeaderBean = HeaderBean()) { var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none") data class HeaderBean(var type: String = "none")
} }
} }
data class MuxBean(var enabled: Boolean) 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>) //data class DnsBean(var servers: List<String>)
@@ -27,34 +27,37 @@ const val divisor = 1024F
fun Long.toSpeedString() = toTrafficString() + "/s" fun Long.toSpeedString() = toTrafficString() + "/s"
fun Long.toTrafficString(): String { fun Long.toTrafficString(): String {
if (this == 0L)
return "\t\t\t0\t B"
if (this < threshold) if (this < threshold)
return "$this B" return "${this.toFloat().toShortString()}\t B"
val kib = this / divisor val kib = this / divisor
if (kib < threshold) if (kib < threshold)
return "${kib.toShortString()} KB" return "${kib.toShortString()}\t KB"
val mib = kib / divisor val mib = kib / divisor
if (mib < threshold) if (mib < threshold)
return "${mib.toShortString()} MB" return "${mib.toShortString()}\t MB"
val gib = mib / divisor val gib = mib / divisor
if (gib < threshold) if (gib < threshold)
return "${gib.toShortString()} GB" return "${gib.toShortString()}\t GB"
val tib = gib / divisor val tib = gib / divisor
if (tib < threshold) if (tib < threshold)
return "${tib.toShortString()} TB" return "${tib.toShortString()}\t TB"
val pib = tib / divisor val pib = tib / divisor
if (pib < threshold) if (pib < threshold)
return "${pib.toShortString()} PB" return "${pib.toShortString()}\t PB"
return "∞" return "∞"
} }
private fun Float.toShortString(): String { private fun Float.toShortString(): String {
val s = toString() val s = "%.2f".format(this)
if (s.length <= 4) if (s.length <= 4)
return s return s
return s.substring(0, 4).removeSuffix(".") return s.substring(0, 4).removeSuffix(".")
@@ -3,14 +3,13 @@ package com.v2ray.ang.receiver
import android.app.PendingIntent import android.app.PendingIntent
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle
import android.widget.RemoteViews import android.widget.RemoteViews
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
class WidgetProvider : AppWidgetProvider() { class WidgetProvider : AppWidgetProvider() {
/** /**
@@ -19,10 +18,21 @@ class WidgetProvider : AppWidgetProvider() {
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds) super.onUpdate(context, appWidgetManager, appWidgetIds)
val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")
updateWidgetBackground(context, appWidgetManager, appWidgetIds, isRunning)
}
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch) val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
val intent = Intent(AppConfig.BROADCAST_ACTION_WIDGET_CLICK) val intent = Intent(context, WidgetProvider::class.java)
intent.setAction(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT) val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent) 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) { for (appWidgetId in appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId, remoteViews) appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
@@ -44,7 +54,9 @@ class WidgetProvider : AppWidgetProvider() {
// context.toast(R.string.toast_services_start) // context.toast(R.string.toast_services_start)
Utils.startVService(context) Utils.startVService(context)
} }
val manager = AppWidgetManager.getInstance(context)
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
!isRunning);
} }
} }
} }
@@ -11,11 +11,14 @@ import android.content.IntentFilter
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.Color import android.graphics.Color
import android.net.* import android.net.*
import android.net.VpnService import android.os.Build
import android.os.* import android.os.ParcelFileDescriptor
import android.os.StrictMode
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import android.util.Log
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString import com.v2ray.ang.extension.toSpeedString
@@ -24,22 +27,21 @@ import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import go.Seq
import libv2ray.Libv2ray import libv2ray.Libv2ray
import libv2ray.V2RayVPNServiceSupportsSet import libv2ray.V2RayVPNServiceSupportsSet
import org.jetbrains.anko.doAsync
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
import java.io.File import java.io.File
import java.lang.ref.SoftReference import java.lang.ref.SoftReference
import android.os.Build
import android.util.Log
import go.Seq
import org.jetbrains.anko.doAsync
class V2RayVpnService : VpnService() { class V2RayVpnService : VpnService() {
companion object { companion object {
const val NOTIFICATION_ID = 1 const val NOTIFICATION_ID = 1
const val NOTIFICATION_PENDING_INTENT_CONTENT = 0 const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1 const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
const val NOTIFICATION_ICON_THRESHOLD = 3000
fun startV2Ray(context: Context) { fun startV2Ray(context: Context) {
val intent = Intent(context.applicationContext, V2RayVpnService::class.java) val intent = Intent(context.applicationContext, V2RayVpnService::class.java)
@@ -52,6 +54,7 @@ class V2RayVpnService : VpnService() {
} }
private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback()) private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private var lastQueryTime = 0L
private lateinit var configContent: String private lateinit var configContent: String
private lateinit var mInterface: ParcelFileDescriptor private lateinit var mInterface: ParcelFileDescriptor
val fd: Int get() = mInterface.fd val fd: Int get() = mInterface.fd
@@ -69,36 +72,28 @@ class V2RayVpnService : VpnService() {
* *
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887 * Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/ */
private val defaultNetworkRequest by lazy { private val defaultNetworkRequest by lazy @RequiresApi(Build.VERSION_CODES.P) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { NetworkRequest.Builder()
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build() .build()
} else {
null
}
} }
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
private val defaultNetworkCallback by lazy { private val defaultNetworkCallback by lazy @RequiresApi(Build.VERSION_CODES.P) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { object : ConnectivityManager.NetworkCallback() {
object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) {
override fun onAvailable(network: Network) { setUnderlyingNetworks(arrayOf(network))
setUnderlyingNetworks(arrayOf(network)) }
} override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) { // it's a good idea to refresh capabilities
// it's a good idea to refresh capabilities setUnderlyingNetworks(arrayOf(network))
setUnderlyingNetworks(arrayOf(network)) }
} override fun onLost(network: Network) {
override fun onLost(network: Network) { setUnderlyingNetworks(null)
setUnderlyingNetworks(null)
}
} }
} else {
null
} }
} }
private var listeningForDefaultNetwork = false private var listeningForDefaultNetwork = false
@@ -109,6 +104,7 @@ class V2RayVpnService : VpnService() {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy) StrictMode.setThreadPolicy(policy)
v2rayPoint.packageName = Utils.packagePath(applicationContext) v2rayPoint.packageName = Utils.packagePath(applicationContext)
v2rayPoint.packageCodePath = applicationContext.applicationInfo.nativeLibraryDir + "/"
Seq.setContext(applicationContext) Seq.setContext(applicationContext)
} }
@@ -137,6 +133,7 @@ class V2RayVpnService : VpnService() {
// Configure a builder while parsing the parameters. // Configure a builder while parsing the parameters.
val builder = Builder() val builder = Builder()
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false) val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
val routingMode = defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
parameters.split(" ") parameters.split(" ")
.map { it.split(",") } .map { it.split(",") }
@@ -145,7 +142,20 @@ class V2RayVpnService : VpnService() {
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt()) 'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
's' -> builder.addSearchDomain(it[1]) 's' -> builder.addSearchDomain(it[1])
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2])) '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 {
val addr = it.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}
}
} else {
builder.addRoute(it[1], Integer.parseInt(it[2]))
}
}
'd' -> builder.addDnsServer(it[1]) 'd' -> builder.addDnsServer(it[1])
} }
} }
@@ -190,6 +200,7 @@ class V2RayVpnService : VpnService() {
// Create a new interface using the builder and save the parameters. // Create a new interface using the builder and save the parameters.
mInterface = builder.establish() mInterface = builder.establish()
sendFd() sendFd()
lastQueryTime = System.currentTimeMillis()
startSpeedNotification() startSpeedNotification()
} }
@@ -365,9 +376,17 @@ class V2RayVpnService : VpnService() {
mSubscription = null mSubscription = null
} }
private fun updateNotification(contentText: String) { private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
if (mBuilder != null) { if (mBuilder != null) {
mBuilder?.setContentTitle(contentText) 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()) getNotificationManager().notify(NOTIFICATION_ID, mBuilder?.build())
} }
} }
@@ -383,22 +402,50 @@ class V2RayVpnService : VpnService() {
if (mSubscription == null && if (mSubscription == null &&
v2rayPoint.isRunning && v2rayPoint.isRunning &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) { defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
var last_zero_speed = false var last_zero_speed = false
val outboundTags = defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
outboundTags.remove(TAG_DIRECT)
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS) mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe { .subscribe {
val uplink = v2rayPoint.queryStats("socks", "uplink") val queryTime = System.currentTimeMillis()
val downlink = v2rayPoint.queryStats("socks", "downlink") val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
val zero_speed = (uplink == 0L && downlink == 0L) var proxyTotal = 0L
val text = StringBuilder()
outboundTags.forEach {
val up = v2rayPoint.queryStats(it, "uplink")
val down = v2rayPoint.queryStats(it, "downlink")
if (up + down > 0) {
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
proxyTotal += up + down
}
}
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
val zero_speed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
if (!zero_speed || !last_zero_speed) { if (!zero_speed || !last_zero_speed) {
updateNotification("${cf_name} • ${(uplink / 3).toSpeedString()}↑ ${(downlink / 3).toSpeedString()}↓") if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds)
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
} }
last_zero_speed = zero_speed last_zero_speed = zero_speed
lastQueryTime = queryTime
} }
} }
} }
private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
var n = name ?: "no tag"
n = n.substring(0, Math.min(n.length, 6))
text.append(n)
for (i in n.length..6 step 2) {
text.append("\t")
}
text.append("• ${up.toLong().toSpeedString()}↑ ${down.toLong().toSpeedString()}↓\n")
}
fun stopSpeedNotification() { fun stopSpeedNotification() {
if (mSubscription != null) { if (mSubscription != null) {
@@ -406,7 +453,7 @@ class V2RayVpnService : VpnService() {
mSubscription = null mSubscription = null
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "") val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cf_name) updateNotification(cf_name, 0, 0)
} }
} }
@@ -55,7 +55,6 @@ abstract class BaseDrawerActivity : BaseActivity() {
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle() this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
var activityClass: Class<*>? = null var activityClass: Class<*>? = null
when (mItemToOpenWhenDrawerCloses) { when (mItemToOpenWhenDrawerCloses) {
R.id.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> activityClass = SubSettingActivity::class.java R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> { R.id.logcat -> {
@@ -195,9 +194,7 @@ abstract class BaseDrawerActivity : BaseActivity() {
true true
} }
if (MainActivity::class.java.isAssignableFrom(javaClass)) { if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.server_profile)
} else if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.sub_setting) navigationView.setCheckedItem(R.id.sub_setting)
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) { } else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.settings) navigationView.setCheckedItem(R.id.settings)
@@ -27,12 +27,14 @@ import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper
import android.util.Log import android.util.Log
import com.v2ray.ang.dto.EConfigType
//import com.v2ray.ang.InappBuyActivity //import com.v2ray.ang.InappBuyActivity
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager.configs import com.v2ray.ang.util.AngConfigManager.configs
import kotlinx.coroutines.*
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
companion object { companion object {
@@ -58,6 +60,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private val adapter by lazy { MainRecyclerAdapter(this) } private val adapter by lazy { MainRecyclerAdapter(this) }
private var mItemTouchHelper: ItemTouchHelper? = null private var mItemTouchHelper: ItemTouchHelper? = null
private val testingJobs = ArrayList<Job>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -162,8 +165,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
importBatchConfig(data?.getStringExtra("SCAN_RESULT")) importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
} }
REQUEST_FILE_CHOOSER -> { REQUEST_FILE_CHOOSER -> {
if (resultCode == RESULT_OK) { val uri = data?.data
val uri = data!!.data if (resultCode == RESULT_OK && uri != null) {
readContentFromUri(uri) readContentFromUri(uri)
} }
} }
@@ -240,19 +243,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
R.id.ping_all -> { R.id.ping_all -> {
testingJobs.forEach {
it.cancel()
}
testingJobs.clear()
Utils.closeAllTcpSockets()
for (k in 0 until configs.vmess.count()) { for (k in 0 until configs.vmess.count()) {
configs.vmess[k].testResult = "" configs.vmess[k].testResult = ""
adapter.updateConfigList() adapter.updateConfigList()
} }
for (k in 0 until configs.vmess.count()) { for (k in 0 until configs.vmess.count()) {
if (configs.vmess[k].configType != AppConfig.EConfigType.Custom) { var serverAddress = configs.vmess[k].address
doAsync { var serverPort = configs.vmess[k].port
configs.vmess[k].testResult = Utils.tcping(configs.vmess[k].address, configs.vmess[k].port) if (configs.vmess[k].configType == EConfigType.CUSTOM.value) {
uiThread { 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) adapter.updateSelectedItem(k)
} }
} }
} })
} }
true true
} }
@@ -442,9 +460,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.subscribe { .subscribe {
if (it) { if (it) {
try { try {
val inputStream = contentResolver.openInputStream(uri) contentResolver.openInputStream(uri).use {
val configText = inputStream.bufferedReader().readText() val configText = it?.bufferedReader()?.readText()
importCustomizeConfig(configText) importCustomizeConfig(configText)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -570,4 +589,4 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
drawer_layout.closeDrawer(GravityCompat.START) drawer_layout.closeDrawer(GravityCompat.START)
return true return true
} }
} }
@@ -8,17 +8,19 @@ import android.view.ViewGroup
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.helper.ItemTouchHelperAdapter import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.util.AngConfigManager import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils 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_qrcode.view.*
import kotlinx.android.synthetic.main.item_recycler_main.view.* import kotlinx.android.synthetic.main.item_recycler_main.view.*
import org.jetbrains.anko.* import org.jetbrains.anko.*
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.v2ray.ang.extension.defaultDPreference
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>() class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
, ItemTouchHelperAdapter { , ItemTouchHelperAdapter {
@@ -49,7 +51,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) { 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 remarks = configs.vmess[position].remarks
val subid = configs.vmess[position].subid val subid = configs.vmess[position].subid
val address = configs.vmess[position].address val address = configs.vmess[position].address
@@ -67,39 +69,40 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.subid.text = "S" holder.subid.text = "S"
} }
if (configType == AppConfig.EConfigType.Vmess) { var shareOptions = share_method.asList()
holder.type.text = "vmess" if (configType == EConfigType.CUSTOM) {
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Custom) {
holder.type.text = mActivity.getString(R.string.server_customize_config) holder.type.text = mActivity.getString(R.string.server_customize_config)
holder.statistics.text = ""//mActivity.getString(R.string.server_customize_config) val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
holder.layout_share.visibility = View.INVISIBLE if (serverOutbound == null) {
} else if (configType == AppConfig.EConfigType.Shadowsocks) { holder.statistics.text = ""
holder.type.text = "shadowsocks" } 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.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 { holder.layout_share.setOnClickListener {
mActivity.selector(null, share_method.asList()) { dialogInterface, i -> mActivity.selector(null, shareOptions) { dialogInterface, i ->
try { try {
when (i) { when (i) {
0 -> { 0 -> {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null) if (configType == EConfigType.CUSTOM) {
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position)) shareFullContent(position)
} else {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
mActivity.alert { mActivity.alert {
customView { customView {
linearLayout { linearLayout {
addView(iv) addView(iv)
}
} }
} }.show()
}.show() }
} }
1 -> { 1 -> {
if (AngConfigManager.share2Clipboard(position) == 0) { if (AngConfigManager.share2Clipboard(position) == 0) {
@@ -108,15 +111,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
mActivity.toast(R.string.toast_failure) mActivity.toast(R.string.toast_failure)
} }
} }
2 -> { 2 -> shareFullContent(position)
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) { else -> mActivity.toast("else")
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
else ->
mActivity.toast("else")
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -125,13 +121,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} }
holder.layout_edit.setOnClickListener { holder.layout_edit.setOnClickListener {
if (configType == AppConfig.EConfigType.Vmess) { if (configType == EConfigType.VMESS) {
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable) 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) 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) 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) 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 { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) { when (viewType) {
VIEW_TYPE_ITEM -> VIEW_TYPE_ITEM ->
@@ -265,4 +269,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition) updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
return true return true
} }
override fun onItemMoveCompleted() {
AngConfigManager.storeConfigFile()
}
} }
@@ -119,10 +119,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
REQUEST_FILE_CHOOSER -> REQUEST_FILE_CHOOSER -> {
if (resultCode == RESULT_OK) { val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
try { try {
val uri = data!!.data
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)) val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val text = QRCodeDecoder.syncDecodeQRCode(bitmap) val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text) finished(text)
@@ -131,6 +131,7 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
toast(e.message.toString()) toast(e.message.toString())
} }
} }
}
} }
} }
} }
@@ -2,7 +2,6 @@ package com.v2ray.ang.util
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
@@ -15,13 +14,11 @@ import com.v2ray.ang.AppConfig.SS_PROTOCOL
import com.v2ray.ang.AppConfig.VMESS_PROTOCOL import com.v2ray.ang.AppConfig.VMESS_PROTOCOL
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.VmessQRCode import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.defaultDPreference import java.net.URI
import org.jetbrains.anko.toast
import java.net.URLDecoder import java.net.URLDecoder
import java.util.* import java.util.*
import java.net.*
import java.math.BigInteger
object AngConfigManager { object AngConfigManager {
private lateinit var app: AngApplication private lateinit var app: AngApplication
@@ -69,7 +66,7 @@ object AngConfigManager {
fun addServer(vmess: AngConfig.VmessBean, index: Int): Int { fun addServer(vmess: AngConfig.VmessBean, index: Int): Int {
try { try {
vmess.configVersion = 2 vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Vmess vmess.configType = EConfigType.VMESS.value
if (index >= 0) { if (index >= 0) {
//edit //edit
@@ -104,16 +101,7 @@ object AngConfigManager {
angConfig.vmess.removeAt(index) angConfig.vmess.removeAt(index)
//移除的是活动的 //移除的是活动的
if (angConfig.index == index) { adjustIndexForRemovalAt(index)
if (angConfig.vmess.count() > 0) {
angConfig.index = 0
} else {
angConfig.index = -1
}
} else if (index < angConfig.index)//移除活动之前的
{
angConfig.index--
}
storeConfigFile() storeConfigFile()
} catch (e: Exception) { } catch (e: Exception) {
@@ -123,6 +111,19 @@ object AngConfigManager {
return 0 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 { fun swapServer(fromPosition: Int, toPosition: Int): Int {
try { try {
Collections.swap(angConfig.vmess, fromPosition, toPosition) Collections.swap(angConfig.vmess, fromPosition, toPosition)
@@ -133,7 +134,7 @@ object AngConfigManager {
} else if (index == toPosition) { } else if (index == toPosition) {
angConfig.index = fromPosition angConfig.index = fromPosition
} }
storeConfigFile() //storeConfigFile()
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return -1 return -1
@@ -208,14 +209,14 @@ object AngConfigManager {
return app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") return app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
} }
fun currConfigType(): Int { fun currConfigType(): EConfigType? {
if (angConfig.index < 0 if (angConfig.index < 0
|| angConfig.vmess.count() <= 0 || angConfig.vmess.count() <= 0
|| angConfig.index > angConfig.vmess.count() - 1 || 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 { fun currConfigName(): String {
@@ -241,7 +242,7 @@ object AngConfigManager {
/** /**
* import config form qrcode or... * import config form qrcode or...
*/ */
fun importConfig(server: String?, subid: String): Int { fun importConfig(server: String?, subid: String, removedSelectedServer: AngConfig.VmessBean?): Int {
try { try {
if (server == null || TextUtils.isEmpty(server)) { if (server == null || TextUtils.isEmpty(server)) {
return R.string.toast_none_data return R.string.toast_none_data
@@ -252,7 +253,11 @@ object AngConfigManager {
if (server.startsWith(VMESS_PROTOCOL)) { if (server.startsWith(VMESS_PROTOCOL)) {
val indexSplit = server.indexOf("?") 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) vmess = ResolveVmess4Kitsunebi(server)
} else { } else {
@@ -271,7 +276,7 @@ object AngConfigManager {
return R.string.toast_incorrect_protocol return R.string.toast_incorrect_protocol
} }
vmess.configType = AppConfig.EConfigType.Vmess vmess.configType = EConfigType.VMESS.value
vmess.security = "auto" vmess.security = "auto"
vmess.network = "tcp" vmess.network = "tcp"
vmess.headerType = "none" vmess.headerType = "none"
@@ -361,6 +366,12 @@ object AngConfigManager {
} else { } else {
return R.string.toast_incorrect_protocol 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) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return -1 return -1
@@ -368,6 +379,61 @@ object AngConfigManager {
return 0 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 { private fun ResolveVmess4Kitsunebi(server: String): AngConfig.VmessBean {
val vmess = AngConfig.VmessBean() val vmess = AngConfig.VmessBean()
@@ -413,7 +479,7 @@ object AngConfigManager {
} }
val vmess = angConfig.vmess[index] val vmess = angConfig.vmess[index]
if (angConfig.vmess[index].configType == AppConfig.EConfigType.Vmess) { if (angConfig.vmess[index].configType == EConfigType.VMESS.value) {
val vmessQRCode = VmessQRCode() val vmessQRCode = VmessQRCode()
vmessQRCode.v = vmess.configVersion.toString() vmessQRCode.v = vmess.configVersion.toString()
@@ -431,7 +497,7 @@ object AngConfigManager {
val conf = VMESS_PROTOCOL + Utils.encode(json) val conf = VMESS_PROTOCOL + Utils.encode(json)
return conf 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 remark = "#" + Utils.urlEncode(vmess.remarks)
val url = String.format("%s:%s@%s:%s", val url = String.format("%s:%s@%s:%s",
vmess.security, vmess.security,
@@ -439,7 +505,7 @@ object AngConfigManager {
vmess.address, vmess.address,
vmess.port) vmess.port)
return SS_PROTOCOL + Utils.encode(url) + remark 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 remark = "#" + Utils.urlEncode(vmess.remarks)
val url = String.format("%s:%s", val url = String.format("%s:%s",
vmess.address, vmess.address,
@@ -548,7 +614,7 @@ object AngConfigManager {
//add //add
val vmess = AngConfig.VmessBean() val vmess = AngConfig.VmessBean()
vmess.configVersion = 2 vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Custom vmess.configType = EConfigType.CUSTOM.value
vmess.guid = guid vmess.guid = guid
vmess.remarks = vmess.guid vmess.remarks = vmess.guid
@@ -651,7 +717,7 @@ object AngConfigManager {
fun addCustomServer(vmess: AngConfig.VmessBean, index: Int): Int { fun addCustomServer(vmess: AngConfig.VmessBean, index: Int): Int {
try { try {
vmess.configVersion = 2 vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Custom vmess.configType = EConfigType.CUSTOM.value
if (index >= 0) { if (index >= 0) {
//edit //edit
@@ -676,7 +742,7 @@ object AngConfigManager {
fun addShadowsocksServer(vmess: AngConfig.VmessBean, index: Int): Int { fun addShadowsocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
try { try {
vmess.configVersion = 2 vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Shadowsocks vmess.configType = EConfigType.SHADOWSOCKS.value
if (index >= 0) { if (index >= 0) {
//edit //edit
@@ -701,7 +767,7 @@ object AngConfigManager {
fun addSocksServer(vmess: AngConfig.VmessBean, index: Int): Int { fun addSocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
try { try {
vmess.configVersion = 2 vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Socks vmess.configType = EConfigType.SOCKS.value
if (index >= 0) { if (index >= 0) {
//edit //edit
@@ -728,6 +794,17 @@ object AngConfigManager {
if (servers == null) { if (servers == null) {
return 0 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) removeServerViaSubid(subid)
// var servers = server // var servers = server
@@ -738,7 +815,7 @@ object AngConfigManager {
var count = 0 var count = 0
servers.lines() servers.lines()
.forEach { .forEach {
val resId = importConfig(it, subid) val resId = importConfig(it, subid, removedSelectedServer)
if (resId == 0) { if (resId == 0) {
count++ count++
} }
@@ -778,6 +855,7 @@ object AngConfigManager {
for (k in configs.vmess.count() - 1 downTo 0) { for (k in configs.vmess.count() - 1 downTo 0) {
if (configs.vmess[k].subid.equals(subid)) { if (configs.vmess[k].subid.equals(subid)) {
angConfig.vmess.removeAt(k) angConfig.vmess.removeAt(k)
adjustIndexForRemovalAt(k)
} }
} }
@@ -823,4 +901,4 @@ object AngConfigManager {
return 0 return 0
} }
} }
@@ -16,7 +16,7 @@ object AppManagerUtil {
val apps = ArrayList<AppInfo>() val apps = ArrayList<AppInfo>()
for (pkg in packages) { for (pkg in packages) {
if (!pkg.hasInternetPermission) continue if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
val applicationInfo = pkg.applicationInfo val applicationInfo = pkg.applicationInfo
@@ -40,4 +40,4 @@ object AppManagerUtil {
val permissions = requestedPermissions val permissions = requestedPermissions
return permissions?.any { it == Manifest.permission.INTERNET } ?: false return permissions?.any { it == Manifest.permission.INTERNET } ?: false
} }
} }
@@ -14,40 +14,33 @@ import kotlin.collections.HashMap
import android.app.ActivityManager import android.app.ActivityManager
import android.content.ClipData import android.content.ClipData
import android.content.Intent import android.content.Intent
import android.content.res.AssetManager
import android.net.Uri import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import android.text.TextUtils import android.text.TextUtils
import android.text.method.ScrollingMovementMethod
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
import android.view.View
import android.webkit.URLUtil import android.webkit.URLUtil
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.responseLength import com.v2ray.ang.extension.responseLength
import com.v2ray.ang.extension.v2RayApplication import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.service.V2RayVpnService import com.v2ray.ang.service.V2RayVpnService
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import kotlinx.android.synthetic.main.activity_logcat.* import kotlinx.coroutines.isActive
import me.dozen.dpreference.DPreference import me.dozen.dpreference.DPreference
import org.jetbrains.anko.toast 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.IOException
import java.io.InputStreamReader
import java.net.* 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 libv2ray.Libv2ray
import kotlin.coroutines.coroutineContext
object Utils { object Utils {
val tcpTestingSockets = ArrayList<Socket?>()
/** /**
* convert string to editalbe for kotlin * convert string to editalbe for kotlin
* *
@@ -102,7 +95,7 @@ object Utils {
try { try {
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(null, content) val clipData = ClipData.newPlainText(null, content)
cmb.primaryClip = clipData cmb.setPrimaryClip(clipData)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -320,7 +313,7 @@ object Utils {
if (AngConfigManager.genStoreV2rayConfig(-1)) { if (AngConfigManager.genStoreV2rayConfig(-1)) {
val configContent = AngConfigManager.currGeneratedV2rayConfig() val configContent = AngConfigManager.currGeneratedV2rayConfig()
val configType = AngConfigManager.currConfigType() val configType = AngConfigManager.currConfigType()
if (configType == AppConfig.EConfigType.Custom) { if (configType == EConfigType.CUSTOM) {
try { try {
Libv2ray.testConfig(configContent) Libv2ray.testConfig(configContent)
} catch (e: Exception) { } catch (e: Exception) {
@@ -448,7 +441,6 @@ object Utils {
return path return path
} }
/** /**
* readTextFromAssets * readTextFromAssets
*/ */
@@ -483,10 +475,13 @@ object Utils {
/** /**
* tcping * tcping
*/ */
fun tcping(url: String, port: Int): String { suspend fun tcping(url: String, port: Int): String {
var time = -1L var time = -1L
for (k in 0 until 2) { for (k in 0 until 2) {
val one = socketConnectTime(url, port) val one = socketConnectTime(url, port)
if (!coroutineContext.isActive) {
break
}
if (one != -1L ) if (one != -1L )
if(time == -1L || one < time) { if(time == -1L || one < time) {
time = one time = one
@@ -497,19 +492,35 @@ object Utils {
fun socketConnectTime(url: String, port: Int): Long { fun socketConnectTime(url: String, port: Int): Long {
try { try {
val socket = Socket()
synchronized(this) {
tcpTestingSockets.add(socket)
}
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
val socket = Socket(url, port) socket.connect(InetSocketAddress(url, port))
val time = System.currentTimeMillis() - start val time = System.currentTimeMillis() - start
synchronized(this) {
tcpTestingSockets.remove(socket)
}
socket.close() socket.close()
return time return time
} catch (e: UnknownHostException) { } catch (e: UnknownHostException) {
e.printStackTrace() e.printStackTrace()
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
return -1 return -1
} }
fun closeAllTcpSockets() {
synchronized(this) {
tcpTestingSockets.forEach {
it?.close()
}
tcpTestingSockets.clear()
}
}
} }
@@ -1,5 +1,6 @@
package com.v2ray.ang.util package com.v2ray.ang.util
import android.content.Context
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
@@ -9,12 +10,15 @@ import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AngConfig.VmessBean import com.v2ray.ang.dto.AngConfig.VmessBean
import com.v2ray.ang.dto.V2rayConfig import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONArray import org.json.JSONArray
import com.google.gson.JsonObject import com.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 { object V2rayConfigUtil {
private val requestObj: JsonObject by lazy { private val requestObj: JsonObject by lazy {
@@ -41,19 +45,10 @@ object V2rayConfigUtil {
// return result // return result
// } // }
if (vmess.configType == AppConfig.EConfigType.Vmess) { if (vmess.configType == EConfigType.CUSTOM.value) {
result = getV2rayConfigType1(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Custom) {
result = getV2rayConfigType2(app, vmess) result = getV2rayConfigType2(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Shadowsocks) { } else {
result = getV2rayConfigType1(app, vmess) 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) 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的客户端配置文件 * 生成v2ray的客户端配置文件
*/ */
@@ -116,6 +127,7 @@ object V2rayConfigUtil {
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "") val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
result.status = true result.status = true
result.content = jsonConfig result.content = jsonConfig
parseDomainNameAndTag(app, jsonConfig)
return result return result
} catch (e: Exception) { } catch (e: Exception) {
@@ -164,8 +176,12 @@ object V2rayConfigUtil {
try { try {
val outbound = v2rayConfig.outbounds[0] val outbound = v2rayConfig.outbounds[0]
when (vmess.configType) { val configType = EConfigType.fromInt(vmess.configType)
AppConfig.EConfigType.Vmess -> { if (configType != null) {
outbound.protocol = configType.name.toLowerCase()
}
when (configType) {
EConfigType.VMESS -> {
outbound.settings?.servers = null outbound.settings?.servers = null
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0) val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
@@ -183,10 +199,8 @@ object V2rayConfigUtil {
//远程服务器底层传输配置 //远程服务器底层传输配置
outbound.streamSettings = boundStreamSettings(vmess) outbound.streamSettings = boundStreamSettings(vmess)
outbound.protocol = "vmess"
} }
AppConfig.EConfigType.Shadowsocks -> { EConfigType.SHADOWSOCKS -> {
outbound.settings?.vnext = null outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0) val server = outbound.settings?.servers?.get(0)
@@ -199,10 +213,8 @@ object V2rayConfigUtil {
//Mux //Mux
outbound.mux?.enabled = false outbound.mux?.enabled = false
outbound.protocol = "shadowsocks"
} }
AppConfig.EConfigType.Socks -> { EConfigType.SOCKS -> {
outbound.settings?.vnext = null outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0) val server = outbound.settings?.servers?.get(0)
@@ -211,21 +223,22 @@ object V2rayConfigUtil {
//Mux //Mux
outbound.mux?.enabled = false outbound.mux?.enabled = false
outbound.protocol = "socks"
}
else -> {
} }
} }
var serverDomain: String val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
if(Utils.isIpv6Address(vmess.address)) { String.format("[%s]:%s", vmess.address, vmess.port)
serverDomain = String.format("[%s]:%s", vmess.address, vmess.port)
} else { } 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) 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) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false return false
@@ -246,7 +259,7 @@ object V2rayConfigUtil {
//streamSettings //streamSettings
when (streamSettings.network) { when (streamSettings.network) {
"kcp" -> { "kcp" -> {
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean() val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
kcpsettings.mtu = 1350 kcpsettings.mtu = 1350
kcpsettings.tti = 50 kcpsettings.tti = 50
kcpsettings.uplinkCapacity = 12 kcpsettings.uplinkCapacity = 12
@@ -254,34 +267,33 @@ object V2rayConfigUtil {
kcpsettings.congestion = false kcpsettings.congestion = false
kcpsettings.readBufferSize = 1 kcpsettings.readBufferSize = 1
kcpsettings.writeBufferSize = 1 kcpsettings.writeBufferSize = 1
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean.HeaderBean() kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
kcpsettings.header.type = vmess.headerType kcpsettings.header.type = vmess.headerType
streamSettings.kcpsettings = kcpsettings streamSettings.kcpSettings = kcpsettings
} }
"ws" -> { "ws" -> {
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean() val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
wssettings.connectionReuse = true
val host = vmess.requestHost.trim() val host = vmess.requestHost.trim()
val path = vmess.path.trim() val path = vmess.path.trim()
if (!TextUtils.isEmpty(host)) { if (!TextUtils.isEmpty(host)) {
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean() wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean.HeadersBean()
wssettings.headers.Host = host wssettings.headers.Host = host
} }
if (!TextUtils.isEmpty(path)) { if (!TextUtils.isEmpty(path)) {
wssettings.path = 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 tlssettings.allowInsecure = true
if (!TextUtils.isEmpty(host)) { if (!TextUtils.isEmpty(host)) {
tlssettings.serverName = host tlssettings.serverName = host
} }
streamSettings.tlssettings = tlssettings streamSettings.tlsSettings = tlssettings
} }
"h2" -> { "h2" -> {
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpsettingsBean() val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
val host = vmess.requestHost.trim() val host = vmess.requestHost.trim()
val path = vmess.path.trim() val path = vmess.path.trim()
@@ -289,31 +301,30 @@ object V2rayConfigUtil {
httpsettings.host = host.split(",").map { it.trim() } httpsettings.host = host.split(",").map { it.trim() }
} }
httpsettings.path = path httpsettings.path = path
streamSettings.httpsettings = httpsettings streamSettings.httpSettings = httpsettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean() val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true tlssettings.allowInsecure = true
streamSettings.tlssettings = tlssettings streamSettings.tlsSettings = tlssettings
} }
"quic" -> { "quic" -> {
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean() val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
val host = vmess.requestHost.trim() val host = vmess.requestHost.trim()
val path = vmess.path.trim() val path = vmess.path.trim()
quicsettings.security = host quicsettings.security = host
quicsettings.key = path quicsettings.key = path
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean.HeaderBean() quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean.HeaderBean()
quicsettings.header.type = vmess.headerType quicsettings.header.type = vmess.headerType
streamSettings.quicsettings = quicsettings streamSettings.quicSettings = quicsettings
} }
else -> { else -> {
//tcp带http伪装 //tcp带http伪装
if (vmess.headerType == "http") { if (vmess.headerType == "http") {
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean() val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
tcpSettings.connectionReuse = true tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean.HeaderBean()
tcpSettings.header.type = vmess.headerType tcpSettings.header.type = vmess.headerType
// if (requestObj.has("headers") // if (requestObj.has("headers")
@@ -619,42 +630,58 @@ object V2rayConfigUtil {
} }
} }
private fun parseDomainName(jsonConfig: String): String { private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
try { try {
val jObj = JSONObject(jsonConfig) val jObj = JSONObject(jsonConfig)
var domainName: String var domainName = ""
val tags = LinkedHashSet<String>()
if (jObj.has("outbound")) { if (jObj.has("outbound")) {
domainName = parseDomainName(jObj.optJSONObject("outbound")) val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
if (!TextUtils.isEmpty(domainName)) { domainName = domain
return domainName if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
} }
} }
if (jObj.has("outbounds")) { if (jObj.has("outbounds")) {
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) { for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
domainName = parseDomainName(jObj.optJSONArray("outbounds").getJSONObject(i)) val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outbounds").getJSONObject(i))
if (!TextUtils.isEmpty(domainName)) { if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
return domainName domainName = domain
}
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
} }
} }
} }
if (jObj.has("outboundDetour")) { if (jObj.has("outboundDetour")) {
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) { for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
domainName = parseDomainName(jObj.optJSONArray("outboundDetour").getJSONObject(i)) val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outboundDetour").getJSONObject(i))
if (!TextUtils.isEmpty(domainName)) { if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
return 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) { } catch (e: Exception) {
e.printStackTrace() 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 { try {
if (outbound.has("settings")) { if (outbound.has("settings")) {
var vnext: JSONArray? val vnext: JSONArray?
if (outbound.optJSONObject("settings").has("vnext")) { if (outbound.optJSONObject("settings").has("vnext")) {
// vmess // vmess
vnext = outbound.optJSONObject("settings").optJSONArray("vnext") vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
@@ -662,22 +689,22 @@ object V2rayConfigUtil {
// shadowsocks or socks // shadowsocks or socks
vnext = outbound.optJSONObject("settings").optJSONArray("servers") vnext = outbound.optJSONObject("settings").optJSONArray("servers")
} else { } else {
return "" return Pair("", tag)
} }
for (i in 0..(vnext.length() - 1)) { for (i in 0..(vnext.length() - 1)) {
val item = vnext.getJSONObject(i) val item = vnext.getJSONObject(i)
val address = item.getString("address") val address = item.getString("address")
val port = item.getString("port") val port = item.getString("port")
if(Utils.isIpv6Address(address)) { if(Utils.isIpv6Address(address)) {
return String.format("[%s]:%s", address, port) return Pair(String.format("[%s]:%s", address, port), tag)
} else { } else {
return String.format("%s:%s", address, port) return Pair(String.format("%s:%s", address, port), tag)
} }
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
return "" return Pair("", tag)
} }
} }
@@ -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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -54,8 +54,10 @@
android:id="@+id/layout_test" android:id="@+id/layout_test"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/connection_test_height" android:layout_height="@dimen/connection_test_height"
android:background="@color/colorPrimary_text" android:background="@drawable/background_test_button"
android:gravity="center|left"> android:gravity="center|left"
android:clickable="true"
android:focusable="true">
<TextView <TextView
android:id="@+id/tv_test_state" android:id="@+id/tv_test_state"
@@ -92,6 +94,8 @@
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:src="@drawable/ic_v_idle" android:src="@drawable/ic_v_idle"
android:clickable="true"
android:focusable="true"
app:layout_anchorGravity="bottom|right|end" /> app:layout_anchorGravity="bottom|right|end" />
</com.github.jorgecastilloprz.FABProgressCircle> </com.github.jorgecastilloprz.FABProgressCircle>
@@ -2,7 +2,10 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" 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.support.v7.widget.AppCompatImageView
android:id="@+id/icon" android:id="@+id/icon"
@@ -40,6 +43,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="false" android:clickable="false"
android:focusable="false"
android:paddingStart="2dp" android:paddingStart="2dp"
android:paddingLeft="2dp" android:paddingLeft="2dp"
android:paddingEnd="6dp" android:paddingEnd="6dp"
@@ -9,10 +9,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="3dp" android:layout_margin="3dp"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:orientation="horizontal"
card_view:cardCornerRadius="5dp"> card_view:cardCornerRadius="5dp">
<LinearLayout <LinearLayout
@@ -21,7 +17,11 @@
android:layout_height="@dimen/server_height" android:layout_height="@dimen/server_height"
android:layout_gravity="center" android:layout_gravity="center"
android: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 <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -111,10 +111,14 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_share" android:id="@+id/layout_share"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/server_height" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:orientation="vertical" 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 <ImageView
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -126,10 +130,13 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_edit" android:id="@+id/layout_edit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/server_height" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:orientation="vertical" 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 <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
@@ -141,10 +148,13 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_remove" android:id="@+id/layout_remove"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/server_height" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
android:orientation="vertical" 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 <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
@@ -6,6 +6,7 @@
android:gravity="center_vertical"> android:gravity="center_vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" <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_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="3dp" android:layout_margin="3dp"
@@ -13,7 +14,8 @@
android:focusable="true" android:focusable="true"
android:foreground="?android:attr/selectableItemBackground" android:foreground="?android:attr/selectableItemBackground"
android:orientation="horizontal" android:orientation="horizontal"
card_view:cardCornerRadius="5dp"> card_view:cardCornerRadius="5dp"
android:nextFocusRight="@+id/layout_edit">
<LinearLayout <LinearLayout
android:id="@+id/info_container" android:id="@+id/info_container"
@@ -62,11 +64,14 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_share" android:id="@+id/layout_share"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/server_height" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:paddingEnd="@dimen/layout_margin_right_height" android:padding="@dimen/layout_margin_spacing"
android:visibility="invisible"> android:visibility="invisible"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
<ImageView <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
@@ -78,10 +83,14 @@
<LinearLayout <LinearLayout
android:id="@+id/layout_edit" android:id="@+id/layout_edit"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="@dimen/server_height" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:orientation="vertical" 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 <ImageView
android:layout_width="@dimen/png_height" android:layout_width="@dimen/png_height"
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <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:id="@+id/layout_switch"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -14,16 +7,9 @@
android:gravity="center" android:gravity="center"
android:orientation="vertical"> android:orientation="vertical">
<ImageView <ImageView
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:src="@mipmap/ic_launcher" /> android:padding="10dp"
android:src="@drawable/ic_v" />
<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>
</LinearLayout> </LinearLayout>
@@ -4,12 +4,7 @@
tools:showIn="navigation_view"> tools:showIn="navigation_view">
<group <group
android:id="@+id/group_main" 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" />
<item <item
android:id="@+id/sub_setting" android:id="@+id/sub_setting"
android:icon="@drawable/ic_subscriptions_white_24dp" android:icon="@drawable/ic_subscriptions_white_24dp"
@@ -83,7 +83,7 @@
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string> <string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
<string name="title_pref_speed_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="title_pref_sniffing_enabled">启用流量探测</string>
<string name="summary_pref_sniffing_enabled">流量探测</string> <string name="summary_pref_sniffing_enabled">流量探测</string>
@@ -136,7 +136,7 @@
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">复制</string> <string name="logcat_copy">复制</string>
<string name="logcat_delete">删除</string> <string name="logcat_delete">删除</string>
<string name="title_export_all">导出全部配置至剪贴板</string> <string name="title_export_all">导出全部(非自定义)配置至剪贴板</string>
<string name="title_sub_setting">订阅设置</string> <string name="title_sub_setting">订阅设置</string>
<string name="sub_setting_remarks">备注</string> <string name="sub_setting_remarks">备注</string>
<string name="sub_setting_url">地址(url)</string> <string name="sub_setting_url">地址(url)</string>
@@ -17,7 +17,7 @@
<string name="toast_services_failure">啟動服務失敗</string> <string name="toast_services_failure">啟動服務失敗</string>
<!--ServerActivity--> <!--ServerActivity-->
<string name="title_server">組態</string> <string name="title_server">設定檔</string>
<string name="menu_item_add_config">新增組態</string> <string name="menu_item_add_config">新增組態</string>
<string name="menu_item_save_config">儲存組態</string> <string name="menu_item_save_config">儲存組態</string>
<string name="menu_item_del_config">刪除組態</string> <string name="menu_item_del_config">刪除組態</string>
@@ -57,7 +57,7 @@
<string name="title_file_chooser">選取一個設定檔</string> <string name="title_file_chooser">選取一個設定檔</string>
<string name="toast_require_file_manager">請安裝檔案總管。</string> <string name="toast_require_file_manager">請安裝檔案總管。</string>
<string name="server_customize_config">自訂組態</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="server_lab_content">內容</string>
<string name="toast_none_data_clipboard">剪貼簿內無資料</string> <string name="toast_none_data_clipboard">剪貼簿內無資料</string>
<string name="toast_invalid_url">網址無效</string> <string name="toast_invalid_url">網址無效</string>
@@ -84,7 +84,7 @@
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string> <string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
<string name="title_pref_speed_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="title_pref_sniffing_enabled">啟用流量探測</string>
<string name="summary_pref_sniffing_enabled">流量探測</string> <string name="summary_pref_sniffing_enabled">流量探測</string>
@@ -104,17 +104,17 @@
<string name="summary_pref_donate">向開發人員捐款</string> <string name="summary_pref_donate">向開發人員捐款</string>
<string name="donate_detail">新增一些實驗性進階功能</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="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="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5代理端口</string> <string name="title_pref_socks_port">SOCKS5代理連接埠</string>
<string name="summary_pref_socks_port">SOCKS5代理端口</string> <string name="summary_pref_socks_port">SOCKS5代理連接埠</string>
<string name="title_pref_http_port">HTTP代理端口</string> <string name="title_pref_http_port">HTTP代理連接埠</string>
<string name="summary_pref_http_port">HTTP代理端口</string> <string name="summary_pref_http_port">HTTP代理連接埠</string>
<string name="title_pref_feedback">回饋</string> <string name="title_pref_feedback">回饋</string>
@@ -138,7 +138,7 @@
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">複製</string> <string name="logcat_copy">複製</string>
<string name="logcat_delete">刪除</string> <string name="logcat_delete">刪除</string>
<string name="title_export_all">匯出全部配置至剪貼簿</string> <string name="title_export_all">匯出全部(非自訂)配置至剪貼簿</string>
<string name="title_sub_setting">訂閱設定</string> <string name="title_sub_setting">訂閱設定</string>
<string name="sub_setting_remarks">備註</string> <string name="sub_setting_remarks">備註</string>
<string name="sub_setting_url">位址(url)</string> <string name="sub_setting_url">位址(url)</string>
@@ -154,7 +154,7 @@
<string name="routing_settings_delete">清除</string> <string name="routing_settings_delete">清除</string>
<string name="routing_settings_scan_replace">掃描並取代</string> <string name="routing_settings_scan_replace">掃描並取代</string>
<string name="routing_settings_scan_append">掃描並附加</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_pending">"檢查連線能力"</string>
<string name="connection_test_testing">"測試中……"</string> <string name="connection_test_testing">"測試中……"</string>
@@ -179,13 +179,13 @@
<string-array name="routing_mode"> <string-array name="routing_mode">
<item>全球</item> <item>全球</item>
<item>略過局域網</item> <item>略過區域網路</item>
<item>略過中國大陸</item> <item>略過中國大陸</item>
<item>略過局域網及中國大陸</item> <item>略過區域網路及中國大陸</item>
</string-array> </string-array>
<string name="title_pref_proxy_sharing_enabled">代理共享</string> <string name="title_pref_proxy_sharing_enabled">代理共享</string>
<string name="summary_pref_proxy_sharing_enabled">綁定代理入口ip到0.0.0.0</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">其他設備可以使用socks/http協定通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網路中啟用以避免未經授權的連接</string>
<string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網絡</string> <string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string>
<string name="toast_malformed_josn">配置格式錯誤</string> <string name="toast_malformed_josn">配置格式錯誤</string>
</resources> </resources>
@@ -66,4 +66,39 @@
<item>IPIfNonMatch</item> <item>IPIfNonMatch</item>
<item>IPOnDemand</item> <item>IPOnDemand</item>
</string-array> </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> </resources>
@@ -3,6 +3,7 @@
<dimen name="bypass_list_header_height">50dp</dimen> <dimen name="bypass_list_header_height">50dp</dimen>
<dimen name="layout_margin_top_height">16dp</dimen> <dimen name="layout_margin_top_height">16dp</dimen>
<dimen name="layout_margin_right_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="edit_height">50dp</dimen>
<dimen name="png_height">24dp</dimen> <dimen name="png_height">24dp</dimen>
<dimen name="server_height">72dp</dimen> <dimen name="server_height">72dp</dimen>
+4 -3
View File
@@ -41,7 +41,7 @@
<string name="server_lab_network">network</string> <string name="server_lab_network">network</string>
<string name="server_lab_more_function">more function</string> <string name="server_lab_more_function">more function</string>
<string name="server_lab_head_type">head type</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_path">path(ws path/h2 path)/QUIC key</string>
<string name="server_lab_stream_security">tls</string> <string name="server_lab_stream_security">tls</string>
<string name="server_lab_allow_insecure">allowInsecure</string> <string name="server_lab_allow_insecure">allowInsecure</string>
@@ -84,7 +84,8 @@
<string name="summary_pref_mux_enabled">Enable maybe speed up network and switch network maybe flash</string> <string name="summary_pref_mux_enabled">Enable maybe speed up network and switch network maybe flash</string>
<string name="title_pref_speed_enabled">Enable speed display</string> <string name="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="title_pref_sniffing_enabled">Enable Sniffing</string>
<string name="summary_pref_sniffing_enabled">Sniffing</string> <string name="summary_pref_sniffing_enabled">Sniffing</string>
@@ -137,7 +138,7 @@
<string name="title_logcat">Logcat</string> <string name="title_logcat">Logcat</string>
<string name="logcat_copy">Copy</string> <string name="logcat_copy">Copy</string>
<string name="logcat_delete">Delete</string> <string name="logcat_delete">Delete</string>
<string name="title_export_all">Export all config to clipboard</string> <string name="title_export_all">Export non-custom configs to clipboard</string>
<string name="title_sub_setting">Subscription setting</string> <string name="title_sub_setting">Subscription setting</string>
<string name="sub_setting_remarks">remarks</string> <string name="sub_setting_remarks">remarks</string>
<string name="sub_setting_url">url</string> <string name="sub_setting_url">url</string>
@@ -13,6 +13,12 @@
<item name="windowNoTitle">true</item> <item name="windowNoTitle">true</item>
</style> </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.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+2 -2
View File
@@ -1,8 +1,8 @@
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
compileSdkVersion 28 compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion '28.0.3' buildToolsVersion buildToolsVer
defaultConfig { defaultConfig {
minSdkVersion 17 minSdkVersion 17
@@ -2,7 +2,9 @@ package me.dozen.dpreference;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
/** /**
@@ -45,10 +47,6 @@ public class DPreference {
PrefAccessor.setInt(mContext, mName, key, value); 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) { public int getPrefInt(final String key, final int defaultValue) {
return PrefAccessor.getInt(mContext, mName, key, defaultValue); return PrefAccessor.getInt(mContext, mName, key, defaultValue);
} }
@@ -61,10 +59,26 @@ public class DPreference {
return PrefAccessor.getLong(mContext, mName, key, defaultValue); 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) { public Set<String> getPrefStringSet(final String key, final Set<String> defaultValue) {
return PrefAccessor.getStringSet(mContext, mName, key, 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) { public void removePreference(final String key) {
PrefAccessor.remove(mContext, mName, key); PrefAccessor.remove(mContext, mName, key);
} }
@@ -4,6 +4,7 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
public class StringSetConverter { public class StringSetConverter {
@@ -13,8 +14,8 @@ public class StringSetConverter {
return gson.toJson(src); return gson.toJson(src);
} }
public static Set<String> decode(String json) { public static LinkedHashSet<String> decode(String json) {
Type setType = new TypeToken<Set<String>>() { Type setType = new TypeToken<LinkedHashSet<String>>() {
}.getType(); }.getType();
return gson.fromJson(json, setType); return gson.fromJson(json, setType);
} }
+3 -3
View File
@@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
ankoVersion=0.10.8 ankoVersion=0.10.8
kotlinVersion=1.3.40 kotlinVersion=1.3.40
supportLibVersion=28.0.0 supportLibVersion=28.0.0
buildToolsVer=28.0.3 buildToolsVer=29.0.3
compileSdkVer=28 compileSdkVer=29
kotlin.incremental=true kotlin.incremental=true
targetSdkVer=28 targetSdkVer=29
+6
View 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