Compare commits

...

30 Commits

Author SHA1 Message Date
2dust
7b47bbe99a Merge pull request #846 from yuhan6665/list-ui
Add subscription remarks in server list
2021-01-16 13:18:27 +08:00
yuhan6665
0fb2165015 Add subscription remarks in server list 2021-01-15 20:44:28 -05:00
2dust
03eeeb9b62 Merge pull request #827 from yuhan6665/master
Update readme
2021-01-02 09:18:45 +08:00
yuhan6665
038daf5fda Update readme 2021-01-01 09:21:10 -05:00
2dust
bfd1387d9b Merge pull request #817 from yuhan6665/fix-manual-update
Fix manual update
2020-12-26 12:03:38 +08:00
yuhan6665
5afec5cf25 Update full config when edit manually 2020-12-25 18:34:58 -05:00
yuhan6665
ec29bdf5bf Refactor for re-use genStoreV2rayConfig() 2020-12-25 18:34:58 -05:00
2dust
57efab093f Merge pull request #803 from yuhan6665/fix-712
Fix toast BadTokenException in OS 7.1.2
2020-12-14 08:21:51 +08:00
2dust
9c92a64811 Merge pull request #801 from yuhan6665/fix-oculus
Fix widget manager null pointer in Oculus
2020-12-14 08:21:41 +08:00
yuhan6665
7ddc82d5cd Fix toast BadTokenException in OS 7.1.2
Apparently recent changes with ViewModel affect the internal of
Activity which lead to toast throwing BadTokenException in OS
7.1.2.
The error is not easily catchable. This library use reflection
to override a key function in WindowManager to catch the error.
I have audit the code of the library.

See https://github.com/PureWriter/ToastCompat for more details
2020-12-13 18:45:04 -05:00
yuhan6665
c286ba18a8 Fix widget manager null pointer in Oculus 2020-12-12 23:11:00 -05:00
2dust
867b5fc880 Merge pull request #795 from rurirei/v2init
init update
2020-12-10 08:25:10 +08:00
rurirei
e8a7fa5320 create init 2020-12-09 17:35:09 +08:00
rurirei
f2f9e55286 do not init all 2020-12-09 17:32:50 +08:00
2dust
4a1c62a67c Merge pull request #787 from yuhan6665/fix-selected
Fix update full config when settings change
2020-12-06 13:48:03 +08:00
yuhan6665
c9a6a459d4 Fix update full config when settings change
Now daemon process does not reference the node list at all and
only depend on a couple of settings like PREF_CURR_CONFIG..
2020-12-05 23:42:44 -05:00
2dust
21fdcf4ccf Merge pull request #783 from yuhan6665/fix-selected
Fix select node of two processes in sync
2020-12-05 18:20:11 +08:00
yuhan6665
7c7a623ae5 Fix select node of two processes in sync
As proxy only mode is added in 1.4.0, I moved the toggle components to the daemon process
When user start service from toggle, the full config is generated from a cached list.
However, this cache is not in sync with the main process list.
In fact, we don't need to generate full config right before the service start. We only
need to when active node is changed.
This way, code logic and daemon process is kept simple
2020-12-04 22:37:31 -05:00
2dust
b3074e9697 Merge pull request #763 from yuhan6665/memory-optimization
Slightly improve memory by reduce unnecessary DPreference usage
2020-11-29 09:31:54 +08:00
yuhan6665
513ebcfa23 Slightly improve memory by reduce unnecessary DPreference usage
See more details in _Ext.kt.
In the future, change will be made to our config storage, so that
the service started through TileService/Widget/ScSwitchActivity will
also not launch main process. That will greatly reduce memory usage
2020-11-28 18:08:50 -05:00
2dust
50d9057f1a Merge pull request #750 from yuhan6665/viewmodel-test
Add ViewModel
2020-11-16 13:22:08 +08:00
2dust
2a563e7884 Merge pull request #747 from liyufan/new_template
Use new issue template
2020-11-16 13:21:40 +08:00
2dust
c69cd18842 Merge pull request #732 from rurirei/isTun2socksRunningi
isTun2socksRunning bool
2020-11-16 13:20:48 +08:00
2dust
7f2ced85a8 Merge pull request #730 from rurirei/stoploop
stoploop update
2020-11-16 13:18:46 +08:00
yuhan6665
6c5eef99b5 Move tests and broadcast listener to ViewModel 2020-11-15 18:37:46 -05:00
yuhan6665
d7c3bae8cc Add MainViewModel
ViewModel is the recommend approach for asynchronous loading
for Activity.
The variable stays even if the Activity is killed temporarily.
2020-11-15 18:37:46 -05:00
LYF
57c98f7c50 Use new issue template 2020-11-13 22:21:35 +08:00
rurirei
49be23c56a bool in return 2020-11-08 11:40:02 +08:00
rurirei
9b658e9a22 IsTun2socksRunning bool 2020-11-08 11:34:49 +08:00
rurirei
aaa84d081f stoploop update 2020-11-08 10:53:23 +08:00
26 changed files with 427 additions and 272 deletions

View File

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

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

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

View File

@@ -6,6 +6,7 @@ import (
type Status struct { type Status struct {
IsRunning bool IsRunning bool
IsTRunning bool
PackageName string PackageName string
PackageCodePath string PackageCodePath string

View File

@@ -17,7 +17,6 @@ import (
v2filesystem "v2ray.com/core/common/platform/filesystem" v2filesystem "v2ray.com/core/common/platform/filesystem"
v2stats "v2ray.com/core/features/stats" v2stats "v2ray.com/core/features/stats"
v2serial "v2ray.com/core/infra/conf/serial" v2serial "v2ray.com/core/infra/conf/serial"
_ "v2ray.com/core/main/distro/all"
v2internet "v2ray.com/core/transport/internet" v2internet "v2ray.com/core/transport/internet"
v2applog "v2ray.com/core/app/log" v2applog "v2ray.com/core/app/log"
@@ -99,9 +98,7 @@ func (v *V2RayPoint) StopLoop() (err error) {
v.v2rayOP.Lock() v.v2rayOP.Lock()
defer v.v2rayOP.Unlock() defer v.v2rayOP.Unlock()
if v.status.IsRunning { if v.status.IsRunning {
close(v.closeChan)
v.shutdownInit() v.shutdownInit()
v.SupportSet.OnEmitStatus(0, "Closed")
} }
return return
} }
@@ -111,6 +108,10 @@ func (v *V2RayPoint) GetIsRunning() bool {
return v.status.IsRunning return v.status.IsRunning
} }
func (v *V2RayPoint) GetIsTRunning() bool {
return v.status.IsTRunning
}
//Delegate Funcation //Delegate Funcation
func (v V2RayPoint) QueryStats(tag string, direct string) int64 { func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
if v.statsManager == nil { if v.statsManager == nil {
@@ -124,12 +125,16 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
} }
func (v *V2RayPoint) shutdownInit() { func (v *V2RayPoint) shutdownInit() {
v.status.IsRunning = false close(v.closeChan)
v.statsManager = nil
v.status.Vpoint.Close() v.status.Vpoint.Close()
v.status.Vpoint = nil v.status.Vpoint = nil
v.statsManager = nil v.status.IsRunning = false
v.escorter.EscortingDown() v.escorter.EscortingDown()
v.SupportSet.Shutdown() v.SupportSet.Shutdown()
v.SupportSet.OnEmitStatus(0, "Closed")
} }
func (v *V2RayPoint) pointloop() error { func (v *V2RayPoint) pointloop() error {
@@ -161,11 +166,13 @@ func (v *V2RayPoint) pointloop() error {
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6)) v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
v.SupportSet.OnEmitStatus(0, "Running") v.SupportSet.OnEmitStatus(0, "Running")
v.status.IsTRunning = false
if !v.ProxyOnly { if !v.ProxyOnly {
if err := v.runTun2socks(); err != nil { if err := v.runTun2socks(); err != nil {
log.Println(err) log.Println(err)
return err return err
} }
v.status.IsTRunning = true
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s", log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS, v.EnableLocalDNS,

View File

@@ -0,0 +1,58 @@
package libv2ray
import (
// The following are necessary as they register handlers in their init functions.
// Required features. Can't remove unless there is replacements.
_ "v2ray.com/core/app/dispatcher"
_ "v2ray.com/core/app/proxyman/inbound"
_ "v2ray.com/core/app/proxyman/outbound"
// Other optional features.
_ "v2ray.com/core/app/dns"
_ "v2ray.com/core/app/log"
_ "v2ray.com/core/app/policy"
_ "v2ray.com/core/app/router"
_ "v2ray.com/core/app/stats"
// Inbound and outbound proxies.
_ "v2ray.com/core/proxy/blackhole"
_ "v2ray.com/core/proxy/dns"
_ "v2ray.com/core/proxy/dokodemo"
_ "v2ray.com/core/proxy/freedom"
_ "v2ray.com/core/proxy/http"
_ "v2ray.com/core/proxy/mtproto"
_ "v2ray.com/core/proxy/shadowsocks"
_ "v2ray.com/core/proxy/socks"
_ "v2ray.com/core/proxy/trojan"
_ "v2ray.com/core/proxy/vless/inbound"
_ "v2ray.com/core/proxy/vless/outbound"
_ "v2ray.com/core/proxy/vmess/inbound"
_ "v2ray.com/core/proxy/vmess/outbound"
// Transport
_ "v2ray.com/core/transport/internet/http"
_ "v2ray.com/core/transport/internet/kcp"
_ "v2ray.com/core/transport/internet/quic"
_ "v2ray.com/core/transport/internet/tcp"
_ "v2ray.com/core/transport/internet/tls"
_ "v2ray.com/core/transport/internet/udp"
_ "v2ray.com/core/transport/internet/websocket"
// Transport headers
_ "v2ray.com/core/transport/internet/headers/http"
_ "v2ray.com/core/transport/internet/headers/noop"
_ "v2ray.com/core/transport/internet/headers/srtp"
_ "v2ray.com/core/transport/internet/headers/tls"
_ "v2ray.com/core/transport/internet/headers/utp"
_ "v2ray.com/core/transport/internet/headers/wechat"
_ "v2ray.com/core/transport/internet/headers/wireguard"
// JSON config support. Choose only one from the two below.
// The following line loads JSON from v2ctl
// _ "v2ray.com/core/main/json"
// The following line loads JSON internally
_ "v2ray.com/core/main/jsonem"
// Load config from file or http(s)
// _ "v2ray.com/core/main/confloader/external"
)

View File

@@ -1,12 +1,13 @@
# v2rayNG # v2rayNG
A V2Ray client for Android A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-17%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/jelly-bean#android-4.2) [![API](https://img.shields.io/badge/API-17%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/jelly-bean#android-4.2)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.4.10-blue.svg)](https://kotlinlang.org) [![Kotlin Version](https://img.shields.io/badge/Kotlin-1.4.10-blue.svg)](https://kotlinlang.org)
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/2dust/v2rayNG)](https://github.com/2dust/v2rayNG/commits/master)
[![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng) [![CodeFactor](https://www.codefactor.io/repository/github/2dust/v2rayng/badge)](https://www.codefactor.io/repository/github/2dust/v2rayng)
[![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases) [![GitHub Releases](https://img.shields.io/github/downloads/2dust/v2rayNG/latest/total?logo=github)](https://github.com/2dust/v2rayNG/releases)
[![Chat on Telegram](https://img.shields.io/badge/Chat%20on-Telegram-brightgreen.svg)](https://t.me/v2rayn)
<a href="https://play.google.com/store/apps/details?id=com.v2ray.ang"> <a href="https://play.google.com/store/apps/details?id=com.v2ray.ang">
<img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" /> <img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="165" height="64" />

View File

@@ -38,6 +38,10 @@ android {
main.java.srcDirs += 'src/main/kotlin' main.java.srcDirs += 'src/main/kotlin'
} }
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
splits { splits {
abi { abi {
enable true enable true
@@ -70,6 +74,10 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
// Androidx ktx
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:livedata:1.1.1'
// Android support library // Android support library
implementation "com.android.support:support-v4:$supportLibVersion" implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion" implementation "com.android.support:appcompat-v7:$supportLibVersion"
@@ -88,6 +96,7 @@ dependencies {
implementation 'me.dm7.barcodescanner:core:1.9.8' implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar' implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation(name: 'libv2ray', ext: 'aar') implementation(name: 'libv2ray', ext: 'aar')
//implementation(name: 'tun2socks', ext: 'aar') //implementation(name: 'tun2socks', ext: 'aar')

View File

@@ -5,6 +5,7 @@ import android.os.Build
import android.widget.Toast import android.widget.Toast
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import me.dozen.dpreference.DPreference import me.dozen.dpreference.DPreference
import me.drakeet.support.toast.ToastCompat
import org.json.JSONObject import org.json.JSONObject
import java.net.URLConnection import java.net.URLConnection
@@ -15,16 +16,20 @@ import java.net.URLConnection
val Context.v2RayApplication: AngApplication val Context.v2RayApplication: AngApplication
get() = applicationContext as AngApplication get() = applicationContext as AngApplication
// Usage note: DPreference use Android ContentProvider to redirect multi process access to main process.
// Currently, RunSoLibV2RayDaemon process will run proxy core, keep minimum configuration and long running
// in the background, support toggle on/off. That means it should NOT use DPreference after the initial
// creation and setup of the service
val Context.defaultDPreference: DPreference val Context.defaultDPreference: DPreference
get() = v2RayApplication.defaultDPreference get() = v2RayApplication.defaultDPreference
inline fun Context.toast(message: Int): Toast = Toast inline fun Context.toast(message: Int): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT) .makeText(this, message, Toast.LENGTH_SHORT)
.apply { .apply {
show() show()
} }
inline fun Context.toast(message: CharSequence): Toast = Toast inline fun Context.toast(message: CharSequence): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT) .makeText(this, message, Toast.LENGTH_SHORT)
.apply { .apply {
show() show()

View File

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

View File

@@ -50,15 +50,16 @@ class WidgetProvider : AppWidgetProvider() {
Utils.startVServiceFromToggle(context) Utils.startVServiceFromToggle(context)
} }
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) { } else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
val manager = AppWidgetManager.getInstance(context) AppWidgetManager.getInstance(context)?.let { manager ->
when (intent.getIntExtra("key", 0)) { when (intent.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> { AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)), updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
true) true)
} }
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> { AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)), updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
false) false)
}
} }
} }
} }

View File

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

View File

@@ -18,6 +18,8 @@ import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.ui.MainActivity import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.SettingsActivity import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil import com.v2ray.ang.util.MessageUtil
@@ -50,14 +52,20 @@ object V2RayServiceManager {
Seq.setContext(context) Seq.setContext(context)
} }
} }
var currentConfigName = "NG"
private var lastQueryTime = 0L private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context, mode: String) { fun startV2Ray(context: Context) {
val intent = if (mode == "VPN") { if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{
context.toast(R.string.toast_services_start)
}
val intent = if (context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
Intent(context.applicationContext, V2RayVpnService::class.java) Intent(context.applicationContext, V2RayVpnService::class.java)
} else { } else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java) Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -132,6 +140,7 @@ object V2RayServiceManager {
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false) v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "") v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN" v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN"
currentConfigName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
try { try {
v2rayPoint.runLoop() v2rayPoint.runLoop()
@@ -235,7 +244,7 @@ object V2RayServiceManager {
mBuilder = NotificationCompat.Builder(service, channelId) mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_v) .setSmallIcon(R.drawable.ic_v)
.setContentTitle(service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) .setContentTitle(currentConfigName)
.setPriority(NotificationCompat.PRIORITY_MIN) .setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true) .setOngoing(true)
.setShowWhen(false) .setShowWhen(false)
@@ -346,13 +355,10 @@ object V2RayServiceManager {
} }
fun stopSpeedNotification() { fun stopSpeedNotification() {
val service = serviceControl?.get()?.getService() ?: return
if (mSubscription != null) { if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats mSubscription?.unsubscribe() //stop queryStats
mSubscription = null mSubscription = null
updateNotification(currentConfigName, 0, 0)
val cfName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cfName, 0, 0)
} }
} }
} }

View File

@@ -10,7 +10,6 @@ import android.os.ParcelFileDescriptor
import android.os.StrictMode import android.os.StrictMode
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.util.Log import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.ui.PerAppProxyActivity import com.v2ray.ang.ui.PerAppProxyActivity
@@ -127,7 +126,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")) builder.setSession(V2RayServiceManager.currentConfigName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) { defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {

View File

@@ -1,42 +1,41 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.Manifest import android.Manifest
import android.content.* import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.support.v7.widget.LinearLayoutManager
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_main.*
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.V2rayConfigUtil
import java.lang.ref.SoftReference
import java.net.URL
import android.content.IntentFilter
import android.support.design.widget.NavigationView import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper import android.support.v7.widget.helper.ItemTouchHelper
import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.dto.EConfigType import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import com.v2ray.ang.viewmodel.MainViewModel
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import java.net.URL
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager.configs
import kotlinx.coroutines.*
import libv2ray.Libv2ray
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener { class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
companion object { companion object {
@@ -46,23 +45,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private const val REQUEST_SCAN_URL = 3 private const val REQUEST_SCAN_URL = 3
} }
var isRunning = false
set(value) {
field = value
adapter.changeable = !value
if (value) {
fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
}
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 tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) } private val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -71,7 +56,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
fab.setOnClickListener { fab.setOnClickListener {
if (isRunning) { if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this) Utils.stopVService(this)
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") { } else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
val intent = VpnService.prepare(this) val intent = VpnService.prepare(this)
@@ -85,16 +70,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
} }
layout_test.setOnClickListener { layout_test.setOnClickListener {
if (isRunning) { if (mainViewModel.isRunning.value == true) {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
tv_test_state.text = getString(R.string.connection_test_testing) tv_test_state.text = getString(R.string.connection_test_testing)
GlobalScope.launch(Dispatchers.IO) { mainViewModel.testCurrentServerRealPing()
val result = Utils.testConnection(this@MainActivity, socksPort)
launch(Dispatchers.Main) {
tv_test_state.text = Utils.getEditable(result)
}
}
} else { } else {
// tv_test_state.text = getString(R.string.connection_test_fail) // tv_test_state.text = getString(R.string.connection_test_fail)
} }
@@ -115,6 +93,33 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
toggle.syncState() toggle.syncState()
nav_view.setNavigationItemSelectedListener(this) nav_view.setNavigationItemSelectedListener(this)
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})" version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver()
}
private fun setupViewModelObserver() {
mainViewModel.updateListAction.observe(this, {
val index = it ?: return@observe
if (index >= 0) {
adapter.updateSelectedItem(index)
} else {
adapter.updateConfigList()
}
})
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
mainViewModel.isRunning.observe(this, {
val isRunning = it ?: return@observe
adapter.changeable = !isRunning
if (isRunning) {
fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
})
mainViewModel.startListenBroadcast()
} }
fun startV2Ray() { fun startV2Ray() {
@@ -123,32 +128,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
showCircle() showCircle()
// toast(R.string.toast_services_start) // toast(R.string.toast_services_start)
if (!Utils.startVService(this)) { if (!Utils.startVService(this, AngConfigManager.configs.index)) {
hideCircle() hideCircle()
} }
} }
override fun onStart() {
super.onStart()
isRunning = false
// val intent = Intent(this.applicationContext, V2RayVpnService::class.java)
// intent.`package` = AppConfig.ANG_PACKAGE
// bindService(intent, mConnection, BIND_AUTO_CREATE)
mMsgReceive = ReceiveMessageHandler(this@MainActivity)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onStop() {
super.onStop()
if (mMsgReceive != null) {
unregisterReceiver(mMsgReceive)
mMsgReceive = null
}
}
public override fun onResume() { public override fun onResume() {
super.onResume() super.onResume()
adapter.updateConfigList() adapter.updateConfigList()
@@ -188,7 +172,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
private fun getOptionIntent() = Intent().putExtra("position", -1) private fun getOptionIntent() = Intent().putExtra("position", -1)
.putExtra("isRunning", isRunning) .putExtra("isRunning", mainViewModel.isRunning.value == true)
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.import_qrcode -> { R.id.import_qrcode -> {
@@ -251,30 +235,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
R.id.ping_all -> { R.id.ping_all -> {
tcpingTestScope.coroutineContext[Job]?.cancelChildren() mainViewModel.testAllTcping()
Utils.closeAllTcpSockets()
for (k in 0 until configs.vmess.count()) {
configs.vmess[k].testResult = ""
adapter.updateConfigList()
}
for (k in 0 until configs.vmess.count()) {
var serverAddress = configs.vmess[k].address
var serverPort = configs.vmess[k].port
if (configs.vmess[k].configType == EConfigType.CUSTOM.value) {
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(applicationContext, configs.vmess[k].guid)
?: continue
serverAddress = serverOutbound.getServerAddress() ?: continue
serverPort = serverOutbound.getServerPort() ?: continue
}
tcpingTestScope.launch {
configs.vmess.getOrNull(k)?.let { // check null in case array is modified during testing
it.testResult = Utils.tcping(serverAddress, serverPort)
launch(Dispatchers.Main) {
adapter.updateSelectedItem(k)
}
}
}
}
true true
} }
@@ -514,35 +475,6 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// } // }
// } // }
private
var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(activity: MainActivity) : BroadcastReceiver() {
internal var mReference: SoftReference<MainActivity> = SoftReference(activity)
override fun onReceive(ctx: Context?, intent: Intent?) {
val activity = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
activity?.isRunning = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
activity?.isRunning = false
}
AppConfig.MSG_STATE_START_SUCCESS -> {
activity?.toast(R.string.toast_services_success)
activity?.isRunning = true
}
AppConfig.MSG_STATE_START_FAILURE -> {
activity?.toast(R.string.toast_services_failure)
activity?.isRunning = false
}
AppConfig.MSG_STATE_STOP_SUCCESS -> {
activity?.isRunning = false
}
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
moveTaskToBack(false) moveTaskToBack(false)
@@ -585,7 +517,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} }
R.id.settings -> { R.id.settings -> {
startActivity(Intent(this, SettingsActivity::class.java) startActivity(Intent(this, SettingsActivity::class.java)
.putExtra("isRunning", isRunning)) .putExtra("isRunning", mainViewModel.isRunning.value == true))
} }
R.id.feedback -> { R.id.feedback -> {
Utils.openUri(this, AppConfig.v2rayNGIssues) Utils.openUri(this, AppConfig.v2rayNGIssues)

View File

@@ -64,11 +64,13 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.radio.isChecked = (position == configs.index) holder.radio.isChecked = (position == configs.index)
holder.itemView.setBackgroundColor(Color.TRANSPARENT) holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = test_result holder.test_result.text = test_result
holder.subscription.text = ""
if (TextUtils.isEmpty(subid)) { if (!TextUtils.isEmpty(subid)) {
holder.subid.text = "" for (sub in configs.subItem) {
} else { if (sub.id == subid) {
holder.subid.text = "S" holder.subscription.text = sub.remarks
}
}
} }
var shareOptions = share_method.asList() var shareOptions = share_method.asList()
@@ -143,16 +145,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} else { } else {
mActivity.showCircle() mActivity.showCircle()
Utils.stopVService(mActivity) Utils.stopVService(mActivity)
AngConfigManager.setActiveServer(position)
Observable.timer(500, TimeUnit.MILLISECONDS) Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
mActivity.showCircle() mActivity.showCircle()
if (!Utils.startVService(mActivity)) { if (!Utils.startVService(mActivity, position)) {
mActivity.hideCircle() mActivity.hideCircle()
} }
} }
} }
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -213,7 +213,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder { class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
val subid = itemView.tv_subid val subscription = itemView.tv_subscription
val radio = itemView.btn_radio!! val radio = itemView.btn_radio!!
val name = itemView.tv_name!! val name = itemView.tv_name!!
val test_result = itemView.tv_test_result!! val test_result = itemView.tv_test_result!!

View File

@@ -69,40 +69,32 @@ class Server2Activity : BaseActivity() {
* save server config * save server config
*/ */
fun saveServer(): Boolean { fun saveServer(): Boolean {
var saveSuccess: Boolean
val vmess = configs.vmess[edit_index] val vmess = configs.vmess[edit_index]
vmess.remarks = et_remarks.text.toString() vmess.remarks = et_remarks.text.toString()
if (TextUtils.isEmpty(vmess.remarks)) { if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks) toast(R.string.server_lab_remarks)
saveSuccess = false return false
} }
if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
toast(R.string.toast_success)
saveSuccess = true
} else {
toast(R.string.toast_failure)
saveSuccess = false
}
try { try {
Gson().fromJson<Object>(tv_content.text.toString(), Object::class.java) Gson().fromJson<Object>(tv_content.text.toString(), Object::class.java)
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
toast(R.string.toast_malformed_josn) toast(R.string.toast_malformed_josn)
saveSuccess = false return false
} }
if (saveSuccess) { if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
//update config //update config
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString()) defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success)
finish() finish()
return true return true
} else { } else {
toast(R.string.toast_failure)
return false return false
} }
} }

View File

@@ -112,6 +112,7 @@ class Server3Activity : BaseActivity() {
} }
if (AngConfigManager.addShadowsocksServer(vmess, edit_index) == 0) { if (AngConfigManager.addShadowsocksServer(vmess, edit_index) == 0) {
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
return true return true

View File

@@ -96,6 +96,7 @@ class Server4Activity : BaseActivity() {
} }
if (AngConfigManager.addSocksServer(vmess, edit_index) == 0) { if (AngConfigManager.addSocksServer(vmess, edit_index) == 0) {
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
return true return true

View File

@@ -155,6 +155,7 @@ class ServerActivity : BaseActivity() {
} }
if (AngConfigManager.addServer(vmess, edit_index) == 0) { if (AngConfigManager.addServer(vmess, edit_index) == 0) {
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success) toast(R.string.toast_success)
finish() finish()
return true return true

View File

@@ -1,5 +1,6 @@
package com.v2ray.ang.ui package com.v2ray.ang.ui
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.support.v7.preference.* import android.support.v7.preference.*
@@ -7,7 +8,9 @@ import android.view.View
import com.v2ray.ang.R import com.v2ray.ang.R
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.extension.toast import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
companion object { companion object {
@@ -36,6 +39,8 @@ class SettingsActivity : BaseActivity() {
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6" const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
} }
private val settingsViewModel by lazy { ViewModelProviders.of(this).get(SettingsViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings) setContentView(R.layout.activity_settings)
@@ -43,6 +48,8 @@ class SettingsActivity : BaseActivity() {
title = getString(R.string.title_settings) title = getString(R.string.title_settings)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
settingsViewModel.startListenPreferenceChange()
} }
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
@@ -74,7 +81,7 @@ class SettingsActivity : BaseActivity() {
private fun restartProxy() { private fun restartProxy() {
Utils.stopVService(requireContext()) Utils.stopVService(requireContext())
Utils.startVService(requireContext()) Utils.startVService(requireContext(), AngConfigManager.configs.index)
} }
private fun isRunning(): Boolean { private fun isRunning(): Boolean {

View File

@@ -2,6 +2,7 @@ package com.v2ray.ang.util
import android.graphics.Bitmap import android.graphics.Bitmap
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson import com.google.gson.Gson
import com.v2ray.ang.AngApplication import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
@@ -154,6 +155,10 @@ object AngConfigManager {
angConfig.index = index angConfig.index = index
app.curIndex = index app.curIndex = index
storeConfigFile() storeConfigFile()
if (!genStoreV2rayConfig()) {
Log.d(AppConfig.ANG_PACKAGE, "set active index $index but generate full configuration failed!")
return -1
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
app.curIndex = -1 app.curIndex = -1
@@ -174,35 +179,32 @@ object AngConfigManager {
} }
} }
fun genStoreV2rayConfigIfActive(index: Int) {
if (index == configs.index) {
if (!genStoreV2rayConfig()) {
Log.d(AppConfig.ANG_PACKAGE, "update config $index but generate full configuration failed!")
}
}
}
/** /**
* gen and store v2ray config file * gen and store v2ray config file
*/ */
fun genStoreV2rayConfig(index: Int): Boolean { fun genStoreV2rayConfig(): Boolean {
try { try {
if (angConfig.index < 0 angConfig.vmess.getOrNull(angConfig.index)?.let {
|| angConfig.vmess.count() <= 0 val result = V2rayConfigUtil.getV2rayConfig(app, it)
|| angConfig.index > angConfig.vmess.count() - 1 if (result.status) {
) { app.defaultDPreference.setPrefString(PREF_CURR_CONFIG, result.content)
return false app.defaultDPreference.setPrefString(PREF_CURR_CONFIG_GUID, currConfigGuid())
} app.defaultDPreference.setPrefString(PREF_CURR_CONFIG_NAME, currConfigName())
var index2 = angConfig.index return true
if (index >= 0) { }
index2 = index
}
val result = V2rayConfigUtil.getV2rayConfig(app, angConfig.vmess[index2])
if (result.status) {
app.defaultDPreference.setPrefString(PREF_CURR_CONFIG, result.content)
app.defaultDPreference.setPrefString(PREF_CURR_CONFIG_GUID, currConfigGuid())
app.defaultDPreference.setPrefString(PREF_CURR_CONFIG_NAME, currConfigName())
return true
} else {
return false
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
return false
} }
return false
} }
fun currGeneratedV2rayConfig(): String { fun currGeneratedV2rayConfig(): String {
@@ -586,9 +588,9 @@ object AngConfigManager {
*/ */
fun shareFullContent2Clipboard(index: Int): Int { fun shareFullContent2Clipboard(index: Int): Int {
try { try {
if (AngConfigManager.genStoreV2rayConfig(index)) { val result = V2rayConfigUtil.getV2rayConfig(app, angConfig.vmess[index])
val configContent = app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "") if (result.status) {
Utils.setClipboard(app.applicationContext, configContent) Utils.setClipboard(app.applicationContext, result.content)
} else { } else {
return -1 return -1
} }

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,17 @@
<?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:id="@+id/item_bg" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools"
android:layout_height="wrap_content" android:id="@+id/item_bg"
android:gravity="center_vertical"> android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto" <android.support.v7.widget.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"
card_view:cardCornerRadius="5dp"> app:cardCornerRadius="5dp">
<LinearLayout <LinearLayout
android:id="@+id/info_container" android:id="@+id/info_container"
@@ -18,34 +20,19 @@
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:clickable="true"
android:focusable="true" android:focusable="true"
android:nextFocusRight="@+id/layout_share"> android:nextFocusRight="@+id/layout_share">
<LinearLayout <android.support.v7.widget.AppCompatRadioButton
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_subid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<android.support.v7.widget.AppCompatRadioButton
android:id="@+id/btn_radio" android:id="@+id/btn_radio"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false" android:clickable="false"
android:focusable="false" android:focusable="false"
android:focusableInTouchMode="false" /> android:focusableInTouchMode="false" />
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -85,20 +72,31 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="right" android:orientation="horizontal"
android:orientation="vertical" android:paddingEnd="5dp">
android:paddingEnd="5dp">
<TextView <TextView
android:id="@+id/tv_test_result" android:id="@+id/tv_subscription"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lines="1" android:layout_weight="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:lines="1"
android:textColor="@color/colorPing" android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textSize="10sp" /> android:textColor="@color/colorSubscription"
android:textSize="10sp"
tools:text="Sub" />
<TextView
android:id="@+id/tv_test_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorPing"
android:textSize="10sp"
tools:text="214ms" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
@@ -185,4 +183,4 @@
</LinearLayout> </LinearLayout>
</android.support.v7.widget.CardView> </android.support.v7.widget.CardView>
</LinearLayout> </LinearLayout>

View File

@@ -9,4 +9,5 @@
<color name="icons">#FFFFFF</color> <color name="icons">#FFFFFF</color>
<color name="divider">#BDBDBD</color> <color name="divider">#BDBDBD</color>
<color name="colorPing">#185534</color> <color name="colorPing">#185534</color>
<color name="colorSubscription">#247BA0</color>
</resources> </resources>