Compare commits

...

141 Commits

Author SHA1 Message Date
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
2dust
5628cbee3a Merge pull request #725 from mzz2017/patch-1
fix: the problem that importing SS URL not support standard format
2020-11-05 14:30:56 +08:00
2dust
3dd663d927 Merge pull request #698 from yuhan6665/master
Release AndroidLibV2rayLite 22
2020-11-05 14:30:41 +08:00
mzz
9e49a2dbd9 fix: the problem that importing SS URL not support standard format 2020-11-02 12:37:26 +08:00
2dust
6c199c1687 Merge pull request #662 from yuhan6665/ext-geo
Add external geofile replacement help
2020-10-24 13:35:21 +08:00
yuhan6665
39af5fdb86 Release AndroidLibV2rayLite 22 2020-10-21 21:31:23 -04:00
2dust
7b2794f6be Merge pull request #695 from rurirei/proxyOnly
proxyOnly update
2020-10-22 07:59:19 +08:00
2dust
411d9e5c9a Merge pull request #691 from rurirei/api30
target 30
2020-10-22 07:58:41 +08:00
Rurirei
a57aee9424 proxyOnly update
* do not run Tun2socks under proxyOnly
2020-10-21 15:19:55 +08:00
Rurirei
4602afc67e call stopV2ray on onDestroy 2020-10-21 15:14:44 +08:00
Rurirei
ceb29840f2 BuildTools 30 fix 2020-10-20 09:49:16 +08:00
Rurirei
1c1f130ca7 update badge 2020-10-20 09:42:48 +08:00
Rurirei
afa0eb9375 package visibility 2020-10-20 07:20:37 +08:00
Rurirei
49b682f0f3 target 30
* update dependencies
2020-10-20 07:10:19 +08:00
Rurirei
d5def3bf2f useless anko 2020-10-20 07:06:36 +08:00
2dust
51b97b64f2 Merge pull request #670 from yuhan6665/fix-gson
Fix crash when custom config cannot be parsed by Gson
2020-10-15 08:18:59 +08:00
2dust
3af9ce1a1a Merge pull request #657 from rurirei/native
native fixes
2020-10-15 08:18:48 +08:00
yuhan6665
a5d3dda941 Fix crash when custom config cannot be parsed by Gson 2020-10-14 11:28:32 -04:00
yuhan6665
71a3e300d8 Add external geofile replacement help 2020-10-09 21:34:15 -04:00
Rurirei
030b9a3900 native fixes
* call sendFd() before tun2socks start
2020-10-08 15:50:51 +08:00
Rurirei
24d105a53c catch logs to checkout this error 2020-10-08 15:49:30 +08:00
2dust
45805d6df7 Merge pull request #644 from yuhan6665/clean-dependency
Clean dependency
2020-10-04 19:16:27 +08:00
2dust
4437e6699b Merge pull request #635 from rurirei/diff
issues of VPNService.kt
2020-10-04 19:16:14 +08:00
2dust
fa409f91e4 Merge pull request #626 from yuhan6665/go
Clean up tun2socks
2020-10-04 19:16:00 +08:00
yuhan6665
89be5f077b Say goodbye to Anko
replace with support lib PreferenceManager
replace LayoutInflater
2020-10-03 11:19:46 -04:00
yuhan6665
896889778f Replace Anko with support lib AlertDialog
replace some property setter
2020-10-03 11:19:46 -04:00
yuhan6665
10f705a8b2 Use standard startActivity 2020-10-03 11:19:46 -04:00
yuhan6665
2956fa2030 Replace doasync with Kotlin coroutine
Using a custom coroutine scope, we don't need to keep track of the
running job. We can simply call cancel children for the scope.
2020-10-03 11:19:46 -04:00
yuhan6665
c2a704a6ea Deprecate some anko function
- Replace toast with inline function
- Use standard startActivityForResult
2020-10-03 11:19:46 -04:00
yuhan6665
78cac0cd90 Replace divider with standard library
Also, cleanup some unused classes
2020-10-03 11:19:46 -04:00
Rurirei
3ffb2e8e05 catch exception for lateinit
PropertyNotInitialized exception needs catch for lateinit var
2020-10-03 20:23:43 +08:00
Rurirei
e2d667e0bb sendFd() should not called twice 2020-10-01 17:36:07 +08:00
Rurirei
3ae0777d7f useless bool 2020-10-01 17:07:51 +08:00
Rurirei
5e6348676c ignore err of registerNetworkConnectivity 2020-10-01 15:32:08 +08:00
2dust
df3f1ca3ef Merge pull request #620 from yuhan6665/up-version
Update dependencies
2020-09-29 08:05:46 +08:00
yuhan6665
94f2bec329 Clean up tun2socks 2020-09-27 23:17:47 -04:00
yuhan6665
c54d8fa43a Update dependencies
Fix a delegate annotation for Kotlin 1.4
2020-09-25 18:37:29 -04:00
2dust
5bbbdcf6f2 Merge pull request #610 from yuhan6665/fix-protect
Fix protect() when there is no service
2020-09-21 07:59:55 +08:00
yuhan6665
4abf20fa32 Fix protect() when there is no service 2020-09-20 19:00:14 -04:00
2dust
723727feb9 Merge pull request #607 from yuhan6665/help-proxy-mode
Help proxy mode
2020-09-19 18:56:15 +08:00
yuhan6665
2efd4b741c Add badge, wiki link and dev guide 2020-09-18 17:48:54 -04:00
yuhan6665
83aab0f880 Add help link in mode setting 2020-09-18 17:48:54 -04:00
2dust
66ea17877e Merge pull request #598 from yuhan6665/fix-process
Add missing process for ScSwitchActivity
2020-09-12 19:48:04 +08:00
2dust
26bc985368 Merge pull request #597 from yuhan6665/settings-ui
Settings ui
2020-09-12 19:47:48 +08:00
yuhan6665
5bbf40c784 Grey out per-app proxy in proxy only mode 2020-09-11 21:30:08 -04:00
yuhan6665
6d5c23245c Move version to drawer
Make it easier to check version.
Technically, version should not be selectable setting
2020-09-11 21:30:08 -04:00
yuhan6665
b148290211 Add missing process for ScSwitchActivity 2020-09-11 21:29:58 -04:00
2dust
c473f9bb13 Merge pull request #587 from yuhan6665/proxy-mode
Proxy only mode
2020-09-06 08:09:10 +08:00
2dust
c7c3d27f36 Merge pull request #584 from akiirui/fix-metered
V2RayVpnService.kt: fix OS marks VPN as metered
2020-09-06 08:08:58 +08:00
yuhan6665
bebc6fea13 Add mode in settings 2020-09-04 18:19:51 -04:00
yuhan6665
9271857b1e Remove VPN related logic in toggle components 2020-09-04 18:19:51 -04:00
yuhan6665
7ea78c1840 Make all toggle component run in daemon process
This commit make the two process with clear role:
 - Daemon process will run proxy core, keep minimum configuration
 and long running in the background, support toggle on/off
 - Main process will have all UI, all servers configuration
2020-09-04 18:19:51 -04:00
yuhan6665
ac668788b3 Add proxy only service 2020-09-04 18:19:51 -04:00
yuhan6665
adfcf0a5d9 Fix some trivial IDE warnings 2020-09-04 18:19:51 -04:00
yuhan6665
15b5595797 Refactor service
Break VpnService so that it only control vpn and tun2socks,
Other function is moved to a singleton service manager.
This code makes it possible to add proxy only service next.
2020-09-04 18:19:51 -04:00
Akatsuki
5a18296cb2 V2RayVpnService.kt: use setMetered
setMetered to false let VPN network to inherit its meteredness from its underlying networks.

Revert `addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)`
2020-09-03 02:07:11 +08:00
Akatsuki
1256edbaf5 V2RayVpnService.kt: fix OS marks VPN as metered
VPN apps targeting Build.VERSION_CODES.Q or above will be considered metered by default.

Ref:
https://developer.android.com/reference/android/net/VpnService.Builder#setMetered(boolean)
https://developer.android.com/about/versions/pie/android-9.0-changes-all#network-capabilities-vpn
2020-09-02 13:46:18 +08:00
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
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
yuhan6665
8b149fb52f Fix selected index when update subscription 2020-04-03 18:58:41 -04:00
83 changed files with 1938 additions and 1503 deletions

View File

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

8
.gitignore vendored
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/geosite.dat
V2rayNG/app/src/main/java/com/v2ray/ang/InappBuyActivity.java
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
*.dat
*.jks
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/gradle/wrapper/gradle-wrapper.properties
V2rayNG/app/release/output.json
V2rayNG/app/release/output.json
.idea/
.gradle/

View File

@@ -5,14 +5,16 @@ import (
)
type Status struct {
IsRunning bool
PackageName string
IsRunning bool
IsTRunning bool
PackageName string
PackageCodePath string
Vpoint v2core.Server
}
func CheckVersion() int {
return 20
return 22
}
func (v *Status) GetDataDir() string {
@@ -20,7 +22,7 @@ func (v *Status) GetDataDir() string {
}
func (v *Status) GetApp(name string) string {
return v.PackageName + name
return v.PackageCodePath + name
}
func (v *Status) GetTun2socksArgs(localDNS bool, enableIPv6 bool) (ret []string) {

View File

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

View File

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

View File

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

View File

@@ -11,14 +11,12 @@ import (
"github.com/2dust/AndroidLibV2rayLite/CoreI"
"github.com/2dust/AndroidLibV2rayLite/Process/Escort"
"github.com/2dust/AndroidLibV2rayLite/VPN"
"github.com/2dust/AndroidLibV2rayLite/shippedBinarys"
mobasset "golang.org/x/mobile/asset"
v2core "v2ray.com/core"
v2filesystem "v2ray.com/core/common/platform/filesystem"
v2stats "v2ray.com/core/features/stats"
v2serial "v2ray.com/core/infra/conf/serial"
_ "v2ray.com/core/main/distro/all"
v2internet "v2ray.com/core/transport/internet"
v2applog "v2ray.com/core/app/log"
@@ -44,10 +42,12 @@ type V2RayPoint struct {
closeChan chan struct{}
PackageName string
PackageCodePath string
DomainName string
ConfigureFileContent string
EnableLocalDNS bool
ForwardIpv6 bool
ProxyOnly bool
}
/*V2RayVPNServiceSupportsSet To support Android VPN mode*/
@@ -57,7 +57,6 @@ type V2RayVPNServiceSupportsSet interface {
Shutdown() int
Protect(int) int
OnEmitStatus(int, string) int
SendFd() int
}
/*RunLoop Run V2Ray main loop
@@ -67,6 +66,7 @@ func (v *V2RayPoint) RunLoop() (err error) {
defer v.v2rayOP.Unlock()
//Construct Context
v.status.PackageName = v.PackageName
v.status.PackageCodePath = v.PackageCodePath
if !v.status.IsRunning {
v.closeChan = make(chan struct{})
@@ -80,7 +80,6 @@ func (v *V2RayPoint) RunLoop() (err error) {
if !v.dialer.IsVServerReady() {
log.Println("vServer cannot resolved, shutdown")
v.StopLoop()
v.SupportSet.Shutdown()
}
// stop waiting if manually closed
@@ -99,9 +98,7 @@ func (v *V2RayPoint) StopLoop() (err error) {
v.v2rayOP.Lock()
defer v.v2rayOP.Unlock()
if v.status.IsRunning {
close(v.closeChan)
v.shutdownInit()
v.SupportSet.OnEmitStatus(0, "Closed")
}
return
}
@@ -111,12 +108,16 @@ func (v *V2RayPoint) GetIsRunning() bool {
return v.status.IsRunning
}
func (v *V2RayPoint) GetIsTRunning() bool {
return v.status.IsTRunning
}
//Delegate Funcation
func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
if v.statsManager == nil {
return 0
}
counter := v.statsManager.GetCounter(fmt.Sprintf("inbound>>>%s>>>traffic>>>%s", tag, direct))
counter := v.statsManager.GetCounter(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct))
if counter == nil {
return 0
}
@@ -124,24 +125,19 @@ func (v V2RayPoint) QueryStats(tag string, direct string) int64 {
}
func (v *V2RayPoint) shutdownInit() {
v.status.IsRunning = false
close(v.closeChan)
v.statsManager = nil
v.status.Vpoint.Close()
v.status.Vpoint = nil
v.statsManager = nil
v.status.IsRunning = false
v.escorter.EscortingDown()
v.SupportSet.Shutdown()
v.SupportSet.OnEmitStatus(0, "Closed")
}
func (v *V2RayPoint) pointloop() error {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
log.Println("loading v2ray config")
config, err := v2serial.LoadJSONConfig(strings.NewReader(v.ConfigureFileContent))
if err != nil {
@@ -169,6 +165,21 @@ func (v *V2RayPoint) pointloop() error {
v.SupportSet.Prepare()
v.SupportSet.Setup(v.status.GetVPNSetupArg(v.EnableLocalDNS, v.ForwardIpv6))
v.SupportSet.OnEmitStatus(0, "Running")
v.status.IsTRunning = false
if !v.ProxyOnly {
if err := v.runTun2socks(); err != nil {
log.Println(err)
return err
}
v.status.IsTRunning = true
log.Printf("EnableLocalDNS: %v\nForwardIpv6: %v\nDomainName: %s",
v.EnableLocalDNS,
v.ForwardIpv6,
v.DomainName)
}
return nil
}
@@ -225,17 +236,10 @@ func NewV2RayPoint(s V2RayVPNServiceSupportsSet) *V2RayPoint {
}
func (v V2RayPoint) runTun2socks() error {
shipb := shippedBinarys.FirstRun{Status: v.status}
if err := shipb.CheckAndExport(); err != nil {
log.Println(err)
return err
}
v.escorter.EscortingUp()
go v.escorter.EscortRun(
v.status.GetApp("tun2socks"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "",
v.SupportSet.SendFd)
v.status.GetApp("libtun2socks.so"),
v.status.GetTun2socksArgs(v.EnableLocalDNS, v.ForwardIpv6), "")
return nil
}

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

View File

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

View File

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

View File

@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion buildToolsVer
compileOptions {
targetCompatibility = "8"
@@ -20,34 +20,17 @@ android {
versionName "1.0.2"
}
signingConfigs {
release {
storeFile file("../key.jks")
keyAlias 'ang'
keyPassword '123456'
storePassword '123456'
}
debug {
storeFile file("../key.jks")
keyAlias 'ang'
keyPassword '123456'
storePassword '123456'
}
}
buildTypes {
release {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.release
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
signingConfig signingConfigs.release
}
}
@@ -55,6 +38,10 @@ android {
main.java.srcDirs += 'src/main/kotlin'
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
splits {
abi {
enable true
@@ -79,11 +66,18 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'
implementation project(':dpreference')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
// Androidx ktx
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:livedata:1.1.1'
// Android support library
implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion"
@@ -92,22 +86,17 @@ dependencies {
implementation "com.android.support:preference-v7:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:multidex:1.0.3"
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
// DSL
implementation "org.jetbrains.anko:anko-sdk15:$ankoVersion"
implementation "org.jetbrains.anko:anko-support-v4:$ankoVersion"
implementation "org.jetbrains.anko:anko-appcompat-v7:$ankoVersion"
implementation "org.jetbrains.anko:anko-design:$ankoVersion"
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'io.reactivex:rxjava:1.3.4'
implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.9.4@aar'
implementation 'com.dinuscxj:recycleritemdecoration:1.0.0'
implementation 'io.reactivex:rxkotlin:0.60.0'
implementation 'me.dm7.barcodescanner:core:1.9.8'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'com.github.jorgecastilloprz:fabprogresscircle:1.01@aar'
implementation 'com.beust:klaxon:3.0.1'
implementation 'com.android.support:multidex:1.0.3'
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation(name: 'libv2ray', ext: 'aar')
//implementation(name: 'tun2socks', ext: 'aar')
@@ -127,4 +116,4 @@ repositories {
flatDir {
dirs 'libs'
}
}
}

Binary file not shown.

View File

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

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.v2ray.ang">
<supports-screens
@@ -12,6 +13,11 @@
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<!-- https://developer.android.com/about/versions/11/privacy/package-visibility -->
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
@@ -21,12 +27,14 @@
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> -->
<application
android:name=".AngApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:extractNativeLibs="true"
android:theme="@style/AppTheme">
<activity
android:name=".ui.MainActivity"
@@ -34,20 +42,16 @@
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
@@ -76,7 +80,11 @@
<activity android:name=".ui.SubEditActivity" />
<activity android:name=".ui.ScScannerActivity" />
<activity android:name=".ui.ScSwitchActivity" />
<activity
android:name=".ui.ScSwitchActivity"
android:excludeFromRecents="true"
android:process=":RunSoLibV2RayDaemon"
android:theme="@style/AppTheme.NoActionBar.Translucent" />
<service
android:name=".service.V2RayVpnService"
@@ -93,22 +101,30 @@
android:value="true" />
</service>
<!--<receiver android:name=".receiver.WidgetProvider">-->
<!--<meta-data-->
<!--android:name="android.appwidget.provider"-->
<!--android:resource="@xml/app_widget_provider" />-->
<service android:name=".service.V2RayProxyOnlyService"
android:exported="false"
android:label="@string/app_name"
android:process=":RunSoLibV2RayDaemon">
</service>
<!--<intent-filter>-->
<!--<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />-->
<!--<action android:name="com.v2ray.ang.action.widget.click" />-->
<!--</intent-filter>-->
<!--</receiver>-->
<receiver android:name=".receiver.WidgetProvider"
android:process=":RunSoLibV2RayDaemon">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_widget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.v2ray.ang.action.widget.click" />
<action android:name="com.v2ray.ang.action.activity" />
</intent-filter>
</receiver>
<service
android:name=".service.QSTileService"
android:icon="@drawable/ic_v"
android:label="@string/app_tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
android:name=".service.QSTileService"
android:icon="@drawable/ic_v"
android:label="@string/app_tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:process=":RunSoLibV2RayDaemon">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
@@ -123,7 +139,8 @@
</intent-filter>
</activity>
<receiver android:name=".receiver.TaskerReceiver">
<receiver android:name=".receiver.TaskerReceiver"
android:process=":RunSoLibV2RayDaemon">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />
</intent-filter>
@@ -132,4 +149,4 @@
</application>
</manifest>
</manifest>

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
com.devolver.reigns2
com.utopia.pxview
ch.protonmail.android
com.perol.asdpl.pixivez
com.pinterest
com.paypal.android.p2pmobile
com.arthurivanets.owly
com.rubenmayayo.reddit
com.rayark.cytus2
com.rayark.pluto
com.rayark.implosion
com.fireproofstudios.theroom4
com.netflix.mediaclient
com.instagram.android
com.google.android.apps.hangoutsdialer
com.google.android.talk
com.google.android.apps.plus
com.google.android.apps.pdfviewer
com.google.android.apps.magazines
com.google.android.apps.nbu.files
com.evernote
net.tsapps.appsales
com.google.android.apps.translate
com.google.ar.lens
com.google.android.apps.adm
com.google.android.apps.googleassistant
tw.com.gamer.android.activecenter
org.telegram.plus
com.brave.browser
com.breel.wallpapers18
com.teslacoilsw.launcher
com.lastpass.lpandroid
org.kustom.widget
com.fooview.android.fooview
com.google.android.apps.docs
com.google.android.apps.maps
com.facebook.services
com.facebook.system
com.facebook.katana
com.nianticlabs.ingress.prime.qa
com.vanced.android.youtube
com.nianticproject.ingress
com.quoord.tapatalkpro.activity
org.mozilla.firefox
com.reddit.frontpage
com.google.android.apps.fitness
android
au.com.shiftyjelly.pocketcasts
com.google.android.gms
com.android.providers.telephony
com.resilio.sync
com.google.android.apps.googlevoice
com.discord
com.cradle.iitc_mobile
bbc.mobile.news.ww
be.mygod.vpnhotspot
ch.protonmail.android
co.wanqu.android
com.alphainventor.filemanager
com.amazon.kindle
com.amazon.mshop.android.shopping
com.android.chrome
com.android.providers.downloads
com.android.providers.downloads.ui
com.android.providers.telephony
com.android.settings
com.android.vending
com.android6park.m6park
com.apkpure.aegon
com.apkupdater
com.app.pornhub
com.arthurivanets.owly
com.asahi.tida.tablet
com.authy.authy
com.avmovie
com.ballistiq.artstation
com.binance.dev
com.bitly.app
com.brave.browser
com.brave.browser_beta
com.breel.wallpapers18
com.bvanced.android.youtube
com.chrome.beta
com.chrome.canary
com.chrome.dev
com.cl.newt66y
com.cradle.iitc_mobile
com.cygames.shadowverse
com.devhd.feedly
com.devolver.reigns2
com.discord
com.downloader.video.tumblr
com.driverbrowser
com.dropbox.android
com.duolingo
com.duckduckgo.mobile.android
com.dv.adm
com.estrongs.android.pop
com.estrongs.android.pop.pro
com.evernote
com.facebook.katana
com.facebook.lite
com.facebook.mlite
com.facebook.orca
com.facebook.services
com.facebook.system
com.fastaccess.github
com.felixfilip.scpae
com.fireproofstudios.theroom4
com.firstrowria.pushnotificationtester
com.flyersoft.moonreaderp
com.fooview.android.fooview
com.fvd.eversync
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.gianlu.aria2app
com.github.yeriomin.yalpstore
com.google.android.apps.adm
com.google.android.apps.books
com.google.android.apps.docs
com.google.android.apps.docs.editors.sheets
com.google.android.apps.fitness
com.google.android.apps.googleassistant
com.google.android.apps.googlevoice
com.google.android.apps.hangoutsdialer
com.google.android.apps.inbox
com.google.android.apps.magazines
com.google.android.apps.maps
com.google.android.apps.nbu.files
com.google.android.apps.paidtasks
com.google.android.apps.pdfviewer
com.google.android.apps.photos
com.google.android.apps.plus
com.google.android.apps.translate
com.google.android.gm
com.google.android.gms
com.google.android.gms.setup
com.google.android.googlequicksearchbox
com.google.android.gsf
com.google.android.gsf.login
com.google.android.ims
com.google.android.inputmethod.latin
com.google.android.instantapps.supervisor
com.google.android.keep
com.google.android.music
com.google.android.ogyoutube
com.google.android.partnersetup
com.google.android.play.games
com.google.android.street
com.google.android.syncadapters.calendar
com.google.android.syncadapters.contacts
com.google.android.talk
com.google.android.tts
com.google.android.videos
com.google.android.youtube
com.google.ar.lens
com.hochan.coldsoup
com.ifttt.ifttt
com.imgur.mobile
com.innologica.inoreader
com.instagram.android
com.instapaper.android
com.jarvanh.vpntether
com.kapp.youtube.final
com.klinker.android.twitter_l
com.lastpass.lpandroid
com.linecorp.linelite
com.lingodeer
com.mediapods.tumbpods
com.mgoogle.android.gms
com.microsoft.emmx
com.microsoft.office.powerpoint
com.microsoft.skydrive
com.mixplorer
com.msd.consumerchinese
com.msd.professionalchinese
com.mss2011c.sharehelper
com.netflix.mediaclient
com.newin.nplayer.pro
com.nianticlabs.ingress.prime.qa
com.nianticproject.ingress
com.ninefolders.hd3
com.ninegag.android.app
com.nintendo.zara
com.nytimes.cn
com.oasisfeng.island
com.ocnt.liveapp.hw
com.orekie.search
com.patreon.android
com.paypal.android.p2pmobile
com.perol.asdpl.pixivez
com.pinterest
com.popularapp.periodcalendar
com.popularapp.videodownloaderforinstagram
com.pushbullet.android
com.quoord.tapatalkpro.activity
com.quora.android
com.rayark.cytus2
com.rayark.implosion
com.rayark.pluto
com.reddit.frontpage
com.resilio.sync
com.rhmsoft.edit
com.rubenmayayo.reddit
com.sec.android.app.sbrowser
com.sec.android.app.sbrowser.beta
com.shanga.walli
com.simplehabit.simplehabitapp
com.slack
com.snaptube.premium
com.sololearn
com.sonelli.juicessh
com.spotify.music
com.tencent.huatuo
com.termux
com.teslacoilsw.launcher
com.theinitium.news
com.thomsonreuters.reuters
com.thunkable.android.hritvik00.freenom
com.topjohnwu.magisk
com.tripadvisor.tripadvisor
com.tumblr
com.twitter.android
com.u91porn
com.u9porn
com.ubisoft.dance.justdance2015companion
com.utopia.pxview
com.valvesoftware.android.steam.communimunity
com.valvesoftware.android.steam.community
com.vanced.android.youtube
com.vimeo.android.videoapp
com.vivaldi.browser
com.vivaldi.browser.snapshot
com.vkontakte.android
com.whatsapp
com.wire
com.wuxiangai.refactor
com.xda.labs
com.xvideos.app
com.yandex.browser
com.yandex.browser.beta
com.yandex.browser.alpha
com.z28j.feel
con.medium.reader
de.apkgrabber
de.robv.android.xposed.installer
dk.tacit.android.foldersync.full
es.rafalense.telegram.themes
es.rafalense.themes
flipboard.app
fm.moon.app
fr.gouv.etalab.mastodon
github.tornaco.xposedmoduletest
idm.internet.download.manager
idm.internet.download.manager.plus
io.github.javiewer
io.github.skyhacker2.magnetsearch
io.va.exposed
it.mvilla.android.fenix2
jp.bokete.app.android
jp.naver.line.android
jp.pxv.android
luo.speedometergpspro
mark.via.gp
me.tshine.easymark
net.teeha.android.url_shortener
net.tsapps.appsales
onion.fire
org.fdroid.fdroid
org.freedownloadmanager.fdm
org.kustom.widget
org.mozilla.fennec_aurora
org.mozilla.fenix
org.mozilla.fenix.nightly
org.mozilla.firefox
org.mozilla.firefox_beta
org.mozilla.focus
org.schabi.newpipe
org.telegram.messenger
org.telegram.multi
org.telegram.plus
org.thunderdog.challegram
org.torproject.android
org.torproject.torbrowser_alpha
org.wikipedia
org.xbmc.kodi
pl.zdunex25.updater
videodownloader.downloadvideo.downloader
com.quora.android
com.lingodeer
org.wikipedia
com.ninegag.android.app
com.duolingo
com.patreon.android
com.valvesoftware.android.steam.communimunity
co.wanqu.android
jp.bokete.app.android
com.vkontakte.android
com.amazon.mshop.android.shopping
com.ubisoft.dance.justdance2015companion
com.gameloft.android.anmp.glofta8hm
com.gameloft.android.anmp.glofta9hm
com.binance.dev
com.asahi.tida.tablet
com.theinitium.news
com.driverbrowser
com.thomsonreuters.reuters
com.nytimes.cn
com.android.providers.downloads.ui
com.avmovie
bbc.mobile.news.ww
org.mozilla.focus
io.github.javiewer
com.sonelli.juicessh
con.medium.reader
com.microsoft.skydrive
com.valvesoftware.android.steam.community
com.nintendo.zara
org.torproject.torbrowser_alpha
tv.twitch.android.app
com.shanga.walli
com.whatsapp
com.wire
com.simplehabit.simplehabitapp
tw.com.gamer.android.activecenter
videodownloader.downloadvideo.downloader
uk.co.bbc.learningenglish
com.ted.android

View File

@@ -13,8 +13,8 @@
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"inbounds": [{

View File

@@ -43,6 +43,8 @@ public interface ItemTouchHelperAdapter {
boolean onItemMove(int fromPosition, int toPosition);
void onItemMoveCompleted();
/**
* Called when an item has been dismissed by a swipe.<br/>
* <br/>

View File

@@ -112,6 +112,8 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onItemMoveCompleted();
viewHolder.itemView.setAlpha(ALPHA_FULL);
if (viewHolder instanceof ItemTouchHelperViewHolder) {

View File

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

View File

@@ -11,7 +11,10 @@ object AppConfig {
const val PREF_CURR_CONFIG_GUID = "pref_v2ray_config_guid"
const val PREF_CURR_CONFIG_NAME = "pref_v2ray_config_name"
const val PREF_CURR_CONFIG_DOMAIN = "pref_v2ray_config_domain"
const val PREF_CURR_CONFIG_OUTBOUND_TAGS = "pref_v2ray_config_outbound_tags"
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val PREF_MODE = "pref_mode"
const val VMESS_PROTOCOL: String = "vmess://"
const val SS_PROTOCOL: String = "ss://"
const val SOCKS_PROTOCOL: String = "socks://"
@@ -35,6 +38,7 @@ object AppConfig {
const val androidpackagenamelistUrl = "https://raw.githubusercontent.com/2dust/androidpackagenamelist/master/proxy.txt"
const val v2rayCustomRoutingListUrl = "https://raw.githubusercontent.com/2dust/v2rayCustomRoutingList/master/"
const val v2rayNGIssues = "https://github.com/2dust/v2rayNG/issues"
const val v2rayNGWikiMode = "https://github.com/2dust/v2rayNG/wiki/Mode"
const val promotionUrl = "https://1.2345345.xyz/ads.html"
const val DNS_AGENT = "1.1.1.1"
@@ -50,12 +54,4 @@ object AppConfig {
const val MSG_STATE_STOP = 4
const val MSG_STATE_STOP_SUCCESS = 41
const val MSG_STATE_RESTART = 5
object EConfigType {
val Vmess = 1
val Custom = 2
val Shadowsocks = 3
val Socks = 4
}
}
}

View File

@@ -0,0 +1,12 @@
package com.v2ray.ang.dto
enum class EConfigType(val value: Int) {
VMESS(1),
CUSTOM(2),
SHADOWSOCKS(3),
SOCKS(4);
companion object {
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
}
}

View File

@@ -64,22 +64,21 @@ data class V2rayConfig(
data class StreamSettingsBean(var network: String,
var security: String,
var tcpSettings: TcpsettingsBean?,
var kcpsettings: KcpsettingsBean?,
var wssettings: WssettingsBean?,
var httpsettings: HttpsettingsBean?,
var tlssettings: TlssettingsBean?,
var quicsettings: QuicsettingBean?
var tcpSettings: TcpSettingsBean?,
var kcpSettings: KcpSettingsBean?,
var wsSettings: WsSettingsBean?,
var httpSettings: HttpSettingsBean?,
var tlsSettings: TlsSettingsBean?,
var quicSettings: QuicSettingBean?
) {
data class TcpsettingsBean(var connectionReuse: Boolean = true,
var header: HeaderBean = HeaderBean()) {
data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none",
var request: Any? = null,
var response: Any? = null)
}
data class KcpsettingsBean(var mtu: Int = 1350,
data class KcpSettingsBean(var mtu: Int = 1350,
var tti: Int = 20,
var uplinkCapacity: Int = 12,
var downlinkCapacity: Int = 100,
@@ -90,25 +89,43 @@ data class V2rayConfig(
data class HeaderBean(var type: String = "none")
}
data class WssettingsBean(var connectionReuse: Boolean = true,
var path: String = "",
data class WsSettingsBean(var path: String = "",
var headers: HeadersBean = HeadersBean()) {
data class HeadersBean(var Host: String = "")
}
data class HttpsettingsBean(var host: List<String> = ArrayList<String>(), var path: String = "")
data class HttpSettingsBean(var host: List<String> = ArrayList(),
var path: String = "")
data class TlssettingsBean(var allowInsecure: Boolean = true,
data class TlsSettingsBean(var allowInsecure: Boolean = true,
var serverName: String = "")
data class QuicsettingBean(var security: String = "none",
var key: String = "",
var header: HeaderBean = HeaderBean()) {
data class QuicSettingBean(var security: String = "none",
var key: String = "",
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none")
}
}
data class MuxBean(var enabled: Boolean)
fun getServerAddress(): String? {
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
return settings?.vnext?.get(0)?.address
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
return settings?.servers?.get(0)?.address
}
return null
}
fun getServerPort(): Int? {
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
return settings?.vnext?.get(0)?.port
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
return settings?.servers?.get(0)?.port
}
return null
}
}
//data class DnsBean(var servers: List<String>)

View File

@@ -2,8 +2,10 @@ package com.v2ray.ang.extension
import android.content.Context
import android.os.Build
import android.widget.Toast
import com.v2ray.ang.AngApplication
import me.dozen.dpreference.DPreference
import me.drakeet.support.toast.ToastCompat
import org.json.JSONObject
import java.net.URLConnection
@@ -14,9 +16,24 @@ import java.net.URLConnection
val Context.v2RayApplication: 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
get() = v2RayApplication.defaultDPreference
inline fun Context.toast(message: Int): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
inline fun Context.toast(message: CharSequence): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
fun JSONObject.putOpt(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)!!
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
@@ -27,34 +44,37 @@ const val divisor = 1024F
fun Long.toSpeedString() = toTrafficString() + "/s"
fun Long.toTrafficString(): String {
if (this == 0L)
return "\t\t\t0\t B"
if (this < threshold)
return "$this B"
return "${this.toFloat().toShortString()}\t B"
val kib = this / divisor
if (kib < threshold)
return "${kib.toShortString()} KB"
return "${kib.toShortString()}\t KB"
val mib = kib / divisor
if (mib < threshold)
return "${mib.toShortString()} MB"
return "${mib.toShortString()}\t MB"
val gib = mib / divisor
if (gib < threshold)
return "${gib.toShortString()} GB"
return "${gib.toShortString()}\t GB"
val tib = gib / divisor
if (tib < threshold)
return "${tib.toShortString()} TB"
return "${tib.toShortString()}\t TB"
val pib = tib / divisor
if (pib < threshold)
return "${pib.toShortString()} PB"
return "${pib.toShortString()}\t PB"
return ""
}
private fun Float.toShortString(): String {
val s = toString()
val s = "%.2f".format(this)
if (s.length <= 4)
return s
return s.substring(0, 4).removeSuffix(".")

View File

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

View File

@@ -3,14 +3,14 @@ package com.v2ray.ang.receiver
import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.RemoteViews
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
class WidgetProvider : AppWidgetProvider() {
/**
@@ -18,11 +18,20 @@ class WidgetProvider : AppWidgetProvider() {
*/
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
super.onUpdate(context, appWidgetManager, appWidgetIds)
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
}
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
val intent = Intent(AppConfig.BROADCAST_ACTION_WIDGET_CLICK)
val intent = Intent(context, WidgetProvider::class.java)
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
if (isRunning) {
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme)
} else {
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_grey)
}
for (appWidgetId in appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId, remoteViews)
@@ -30,21 +39,29 @@ class WidgetProvider : AppWidgetProvider() {
}
/**
* 接收窗口小部件点击时发送的广播
* 接收窗口小部件发送的广播
*/
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if (AppConfig.BROADCAST_ACTION_WIDGET_CLICK == intent.action) {
val isRunning = Utils.isServiceRun(context, "com.v2ray.ang.service.V2RayVpnService")
if (isRunning) {
// context.toast(R.string.toast_services_stop)
if (V2RayServiceManager.v2rayPoint.isRunning) {
Utils.stopVService(context)
} else {
// context.toast(R.string.toast_services_start)
Utils.startVService(context)
Utils.startVServiceFromToggle(context)
}
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
AppWidgetManager.getInstance(context)?.let { manager ->
when (intent.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
true)
}
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
false)
}
}
}
}
}
}

View File

@@ -6,19 +6,15 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.drawable.Icon
import android.net.VpnService
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import org.jetbrains.anko.toast
import java.lang.ref.SoftReference
@TargetApi(Build.VERSION_CODES.N)
class QSTileService : TileService() {
@@ -29,11 +25,10 @@ class QSTileService : TileService() {
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v_idle)
} else if (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?.updateTile()
}
@@ -56,11 +51,7 @@ class QSTileService : TileService() {
super.onClick()
when (qsTile.state) {
Tile.STATE_INACTIVE -> {
val intent = VpnService.prepare(this)
if (intent == null)
if (!Utils.startVService(this)) {
toast(R.string.app_tile_first_use)
}
Utils.startVServiceFromToggle(this)
}
Tile.STATE_ACTIVE -> {
Utils.stopVService(this)
@@ -93,4 +84,4 @@ class QSTileService : TileService() {
}
}
}
}
}

View File

@@ -0,0 +1,14 @@
package com.v2ray.ang.service
import android.app.Service
interface ServiceControl {
fun getService(): Service
fun startService(parameters: String)
fun stopService()
fun vpnProtect(socket: Int): Boolean
}

View File

@@ -0,0 +1,43 @@
package com.v2ray.ang.service
import android.app.Service
import android.content.Intent
import android.os.IBinder
import java.lang.ref.SoftReference
class V2RayProxyOnlyService : Service(), ServiceControl {
override fun onCreate() {
super.onCreate()
V2RayServiceManager.serviceControl = SoftReference(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
V2RayServiceManager.startV2rayPoint()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
V2RayServiceManager.stopV2rayPoint()
}
override fun getService(): Service {
return this
}
override fun startService(parameters: String) {
// do nothing
}
override fun stopService() {
stopSelf()
}
override fun vpnProtect(socket: Int): Boolean {
return true
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}

View File

@@ -0,0 +1,364 @@
package com.v2ray.ang.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import android.util.Log
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import go.Seq
import libv2ray.Libv2ray
import libv2ray.V2RayPoint
import libv2ray.V2RayVPNServiceSupportsSet
import rx.Observable
import rx.Subscription
import java.lang.ref.SoftReference
import kotlin.math.min
object V2RayServiceManager {
private const val NOTIFICATION_ID = 1
private const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
private const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
private const val NOTIFICATION_ICON_THRESHOLD = 3000
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private val mMsgReceive = ReceiveMessageHandler()
var serviceControl: SoftReference<ServiceControl>? = null
set(value) {
field = value
val context = value?.get()?.getService()?.applicationContext
context?.let {
v2rayPoint.packageName = Utils.packagePath(context)
v2rayPoint.packageCodePath = context.applicationInfo.nativeLibraryDir + "/"
Seq.setContext(context)
}
}
var currentConfigName = "NG"
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context) {
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)
} else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
private class V2RayCallback : V2RayVPNServiceSupportsSet {
override fun shutdown(): Long {
val serviceControl = serviceControl?.get() ?: return -1
// called by go
// shutdown the whole vpn service
return try {
serviceControl.stopService()
0
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
-1
}
}
override fun prepare(): Long {
return 0
}
override fun protect(l: Long): Long {
val serviceControl = serviceControl?.get() ?: return 0
return if (serviceControl.vpnProtect(l.toInt())) 0 else 1
}
override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0
}
override fun setup(s: String): Long {
val serviceControl = serviceControl?.get() ?: return -1
//Logger.d(s)
return try {
serviceControl.startService(s)
lastQueryTime = System.currentTimeMillis()
startSpeedNotification()
0
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
-1
}
}
}
fun startV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
if (!v2rayPoint.isRunning) {
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
service.registerReceiver(mMsgReceive, mFilter)
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
v2rayPoint.configureFileContent = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
v2rayPoint.enableLocalDNS = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
v2rayPoint.forwardIpv6 = service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
v2rayPoint.domainName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
v2rayPoint.proxyOnly = service.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") != "VPN"
currentConfigName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "NG")
try {
v2rayPoint.runLoop()
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
}
}
}
fun stopV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
if (v2rayPoint.isRunning) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
cancelNotification()
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
}
}
private class ReceiveMessageHandler : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
val serviceControl = serviceControl?.get() ?: return
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// nothing to do
}
AppConfig.MSG_STATE_START -> {
// nothing to do
}
AppConfig.MSG_STATE_STOP -> {
serviceControl.stopService()
}
AppConfig.MSG_STATE_RESTART -> {
startV2rayPoint()
}
}
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
startSpeedNotification()
}
}
}
}
private fun showNotification() {
val service = serviceControl?.get()?.getService() ?: return
val startMainIntent = Intent(service, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(service,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_v)
.setContentTitle(currentConfigName)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
.setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_close_grey_800_24dp,
service.getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent)
//.build()
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
service.startForeground(NOTIFICATION_ID, mBuilder?.build())
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "RAY_NG_M_CH_ID"
val channelName = "V2rayNG Background Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
chan.lightColor = Color.DKGRAY
chan.importance = NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
getNotificationManager()?.createNotificationChannel(chan)
return channelId
}
fun cancelNotification() {
val service = serviceControl?.get()?.getService() ?: return
service.stopForeground(true)
mBuilder = null
mSubscription?.unsubscribe()
mSubscription = null
}
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
if (mBuilder != null) {
if (proxyTraffic < NOTIFICATION_ICON_THRESHOLD && directTraffic < NOTIFICATION_ICON_THRESHOLD) {
mBuilder?.setSmallIcon(R.drawable.ic_v)
} else if (proxyTraffic > directTraffic) {
mBuilder?.setSmallIcon(R.drawable.ic_stat_proxy)
} else {
mBuilder?.setSmallIcon(R.drawable.ic_stat_direct)
}
mBuilder?.setStyle(NotificationCompat.BigTextStyle().bigText(contentText))
mBuilder?.setContentText(contentText) // Emui4.1 need content text even if style is set as BigTextStyle
getNotificationManager()?.notify(NOTIFICATION_ID, mBuilder?.build())
}
}
private fun getNotificationManager(): NotificationManager? {
if (mNotificationManager == null) {
val service = serviceControl?.get()?.getService() ?: return null
mNotificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
return mNotificationManager
}
fun startSpeedNotification() {
val service = serviceControl?.get()?.getService() ?: return
if (mSubscription == null &&
v2rayPoint.isRunning &&
service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
var lastZeroSpeed = false
val outboundTags = service.defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
outboundTags.remove(TAG_DIRECT)
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
val queryTime = System.currentTimeMillis()
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
var proxyTotal = 0L
val text = StringBuilder()
outboundTags.forEach {
val up = v2rayPoint.queryStats(it, "uplink")
val down = v2rayPoint.queryStats(it, "downlink")
if (up + down > 0) {
appendSpeedString(text, it, up / sinceLastQueryInSeconds, down / sinceLastQueryInSeconds)
proxyTotal += up + down
}
}
val directUplink = v2rayPoint.queryStats(TAG_DIRECT, "uplink")
val directDownlink = v2rayPoint.queryStats(TAG_DIRECT, "downlink")
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds)
updateNotification(text.toString(), proxyTotal, directDownlink + directUplink)
}
lastZeroSpeed = zeroSpeed
lastQueryTime = queryTime
}
}
}
private fun appendSpeedString(text: StringBuilder, name: String?, up: Double, down: Double) {
var n = name ?: "no tag"
n = n.substring(0, min(n.length, 6))
text.append(n)
for (i in n.length..6 step 2) {
text.append("\t")
}
text.append("${up.toLong().toSpeedString()}${down.toLong().toSpeedString()}\n")
}
fun stopSpeedNotification() {
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
updateNotification(currentConfigName, 0, 0)
}
}
}

View File

@@ -1,64 +1,28 @@
package com.v2ray.ang.service
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.*
import android.net.VpnService
import android.os.*
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.StrictMode
import android.support.annotation.RequiresApi
import android.support.v4.app.NotificationCompat
import com.v2ray.ang.AppConfig
import android.util.Log
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.Utils
import libv2ray.Libv2ray
import libv2ray.V2RayVPNServiceSupportsSet
import rx.Observable
import rx.Subscription
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import java.lang.ref.SoftReference
import android.os.Build
import android.util.Log
import go.Seq
import org.jetbrains.anko.doAsync
class V2RayVpnService : VpnService() {
companion object {
const val NOTIFICATION_ID = 1
const val NOTIFICATION_PENDING_INTENT_CONTENT = 0
const val NOTIFICATION_PENDING_INTENT_STOP_V2RAY = 1
fun startV2Ray(context: Context) {
val intent = Intent(context.applicationContext, V2RayVpnService::class.java)
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}
private val v2rayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private lateinit var configContent: String
class V2RayVpnService : VpnService(), ServiceControl {
private lateinit var mInterface: ParcelFileDescriptor
val fd: Int get() = mInterface.fd
private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null
/**
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
@@ -69,22 +33,23 @@ class V2RayVpnService : VpnService() {
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/
private val defaultNetworkRequest by lazy @RequiresApi(Build.VERSION_CODES.P) {
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkRequest by lazy {
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
}
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
private val defaultNetworkCallback by lazy @RequiresApi(Build.VERSION_CODES.P) {
@delegate:RequiresApi(Build.VERSION_CODES.P)
private val defaultNetworkCallback by lazy {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
setUnderlyingNetworks(arrayOf(network))
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
// it's a good idea to refresh capabilities
setUnderlyingNetworks(arrayOf(network))
}
@@ -93,15 +58,13 @@ class V2RayVpnService : VpnService() {
}
}
}
private var listeningForDefaultNetwork = false
override fun onCreate() {
super.onCreate()
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
v2rayPoint.packageName = Utils.packagePath(applicationContext)
Seq.setContext(applicationContext)
V2RayServiceManager.serviceControl = SoftReference(this)
}
override fun onRevoke() {
@@ -115,12 +78,12 @@ class V2RayVpnService : VpnService() {
override fun onDestroy() {
super.onDestroy()
cancelNotification()
stopV2Ray()
}
fun setup(parameters: String) {
private fun setup(parameters: String) {
val prepare = VpnService.prepare(this)
val prepare = prepare(this)
if (prepare != null) {
return
}
@@ -129,6 +92,7 @@ class V2RayVpnService : VpnService() {
// Configure a builder while parsing the parameters.
val builder = Builder()
val enableLocalDns = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
val routingMode = defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
parameters.split(" ")
.map { it.split(",") }
@@ -137,7 +101,20 @@ class V2RayVpnService : VpnService() {
'm' -> builder.setMtu(java.lang.Short.parseShort(it[1]).toInt())
's' -> builder.addSearchDomain(it[1])
'a' -> builder.addAddress(it[1], Integer.parseInt(it[2]))
'r' -> builder.addRoute(it[1], Integer.parseInt(it[2]))
'r' -> {
if (routingMode == "1" || routingMode == "3") {
if (it[1] == "::") { //not very elegant, should move Vpn setting in Kotlin, simplify go code
builder.addRoute("2000::", 3)
} else {
resources.getStringArray(R.array.bypass_private_ip_address).forEach { cidr ->
val addr = cidr.split('/')
builder.addRoute(addr[0], addr[1].toInt())
}
}
} else {
builder.addRoute(it[1], Integer.parseInt(it[2]))
}
}
'd' -> builder.addDnsServer(it[1])
}
}
@@ -149,7 +126,7 @@ class V2RayVpnService : VpnService() {
}
}
builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
builder.setSession(V2RayServiceManager.currentConfigName)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
@@ -171,33 +148,42 @@ class V2RayVpnService : VpnService() {
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
try {
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
} catch (e: Exception) {
e.printStackTrace()
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
builder.setMetered(false)
}
// Create a new interface using the builder and save the parameters.
mInterface = builder.establish()
try {
mInterface = builder.establish()!!
} catch (e: Exception) {
// non-nullable lateinit var
e.printStackTrace()
stopV2Ray()
}
sendFd()
startSpeedNotification()
}
fun shutdown() {
stopV2Ray(true)
}
fun sendFd() {
private fun sendFd() {
val fd = mInterface.fileDescriptor
val path = File(Utils.packagePath(applicationContext), "sock_path").absolutePath
doAsync {
GlobalScope.launch(Dispatchers.IO) {
var tries = 0
while (true) try {
Thread.sleep(50L shl tries)
Log.d(packageName, "sendFd tries: " + tries.toString())
Thread.sleep(1000L shl tries)
Log.d(packageName, "sendFd tries: $tries")
LocalSocket().use { localSocket ->
localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM))
localSocket.setFileDescriptorsForSend(arrayOf(fd))
@@ -213,74 +199,27 @@ class V2RayVpnService : VpnService() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startV2ray()
V2RayServiceManager.startV2rayPoint()
return START_STICKY
//return super.onStartCommand(intent, flags, startId)
}
private fun startV2ray() {
if (!v2rayPoint.isRunning) {
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
mFilter.addAction(Intent.ACTION_SCREEN_ON)
mFilter.addAction(Intent.ACTION_SCREEN_OFF)
mFilter.addAction(Intent.ACTION_USER_PRESENT)
registerReceiver(mMsgReceive, mFilter)
} catch (e: Exception) {
}
configContent = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
v2rayPoint.configureFileContent = configContent
v2rayPoint.enableLocalDNS = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)
v2rayPoint.forwardIpv6 = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_FORWARD_IPV6, false)
v2rayPoint.domainName = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, "")
try {
v2rayPoint.runLoop()
} catch (e: Exception) {
Log.d(packageName, e.toString())
}
if (v2rayPoint.isRunning) {
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_SUCCESS, "")
showNotification()
} else {
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "")
cancelNotification()
}
}
// showNotification()
}
private fun stopV2Ray(isForced: Boolean = true) {
// val configName = defaultDPreference.getPrefString(PREF_CURR_CONFIG_GUID, "")
// val emptyInfo = VpnNetworkInfo()
// val info = loadVpnNetworkInfo(configName, emptyInfo)!! + (lastNetworkInfo ?: emptyInfo)
// saveVpnNetworkInfo(configName, info)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (listeningForDefaultNetwork) {
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
listeningForDefaultNetwork = false
}
}
if (v2rayPoint.isRunning) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(packageName, e.toString())
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
} catch (ignored: Exception) {
// ignored
}
}
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "")
cancelNotification()
V2RayServiceManager.stopV2rayPoint()
if (isForced) {
try {
unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
}
//stopSelf has to be called ahead of mInterface.close(). otherwise v2ray core cannot be stooped
//It's strage but true.
//This can be verified by putting stopself() behind and call stopLoop and startLoop
@@ -291,207 +230,26 @@ class V2RayVpnService : VpnService() {
try {
mInterface.close()
} catch (ignored: Exception) {
// ignored
}
}
}
private fun showNotification() {
val startMainIntent = Intent(applicationContext, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(applicationContext,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(applicationContext,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
} else {
// If earlier version channel ID is not used
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
""
}
mBuilder = NotificationCompat.Builder(applicationContext, channelId)
.setSmallIcon(R.drawable.ic_v)
.setContentTitle(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
.setOnlyAlertOnce(true)
.setContentIntent(contentPendingIntent)
.addAction(R.drawable.ic_close_grey_800_24dp,
getString(R.string.notification_action_stop_v2ray),
stopV2RayPendingIntent)
//.build()
//mBuilder?.setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) //取消震动,铃声其他都不好使
startForeground(NOTIFICATION_ID, mBuilder?.build())
override fun getService(): Service {
return this
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(): String {
val channelId = "RAY_NG_M_CH_ID"
val channelName = "V2rayNG Background Service"
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_HIGH)
chan.lightColor = Color.DKGRAY
chan.importance = NotificationManager.IMPORTANCE_NONE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
getNotificationManager().createNotificationChannel(chan)
return channelId
override fun startService(parameters: String) {
setup(parameters)
}
private fun cancelNotification() {
stopForeground(true)
mBuilder = null
mSubscription?.unsubscribe()
mSubscription = null
override fun stopService() {
stopV2Ray(true)
}
private fun updateNotification(contentText: String) {
if (mBuilder != null) {
mBuilder?.setContentTitle(contentText)
getNotificationManager().notify(NOTIFICATION_ID, mBuilder?.build())
}
override fun vpnProtect(socket: Int): Boolean {
return protect(socket)
}
private fun getNotificationManager(): NotificationManager {
if (mNotificationManager == null) {
mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
return mNotificationManager!!
}
fun startSpeedNotification() {
if (mSubscription == null &&
v2rayPoint.isRunning &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
var last_zero_speed = false
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
val uplink = v2rayPoint.queryStats("socks", "uplink")
val downlink = v2rayPoint.queryStats("socks", "downlink")
val zero_speed = (uplink == 0L && downlink == 0L)
if (!zero_speed || !last_zero_speed) {
updateNotification("${cf_name}${(uplink / 3).toSpeedString()}${(downlink / 3).toSpeedString()}")
}
last_zero_speed = zero_speed
}
}
}
fun stopSpeedNotification() {
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
val cf_name = defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cf_name)
}
}
private inner class V2RayCallback : V2RayVPNServiceSupportsSet {
override fun shutdown(): Long {
// called by go
// shutdown the whole vpn service
try {
this@V2RayVpnService.shutdown()
return 0
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
}
override fun prepare(): Long {
return 0
}
override fun protect(l: Long) = (if (this@V2RayVpnService.protect(l.toInt())) 0 else 1).toLong()
override fun onEmitStatus(l: Long, s: String?): Long {
//Logger.d(s)
return 0
}
override fun setup(s: String): Long {
//Logger.d(s)
try {
this@V2RayVpnService.setup(s)
return 0
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
}
override fun sendFd(): Long {
try {
this@V2RayVpnService.sendFd()
} catch (e: Exception) {
Log.d(packageName, e.toString())
return -1
}
return 0
}
}
private var mMsgReceive = ReceiveMessageHandler(this@V2RayVpnService)
private class ReceiveMessageHandler(vpnService: V2RayVpnService) : BroadcastReceiver() {
internal var mReference: SoftReference<V2RayVpnService> = SoftReference(vpnService)
override fun onReceive(ctx: Context?, intent: Intent?) {
val vpnService = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
//Logger.e("ReceiveMessageHandler", intent?.getIntExtra("key", 0).toString())
val isRunning = vpnService?.v2rayPoint!!.isRunning
&& VpnService.prepare(vpnService) == null
if (isRunning) {
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(vpnService, AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// vpnService?.mMsgSend = null
}
AppConfig.MSG_STATE_START -> {
//nothing to do
}
AppConfig.MSG_STATE_STOP -> {
vpnService?.stopV2Ray()
}
AppConfig.MSG_STATE_RESTART -> {
vpnService?.startV2ray()
}
}
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
vpnService?.stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
vpnService?.startSpeedNotification()
}
}
}
}
}

View File

@@ -13,11 +13,7 @@ import android.support.v7.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.view.View
//import com.v2ray.ang.InappBuyActivity
import com.v2ray.ang.R
import org.jetbrains.anko.startActivity
abstract class BaseDrawerActivity : BaseActivity() {
companion object {
@@ -55,11 +51,10 @@ abstract class BaseDrawerActivity : BaseActivity() {
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
var activityClass: Class<*>? = null
when (mItemToOpenWhenDrawerCloses) {
R.id.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> {
startActivity<LogcatActivity>()
startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
return
}
R.id.donate -> {
@@ -195,9 +190,7 @@ abstract class BaseDrawerActivity : BaseActivity() {
true
}
if (MainActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.server_profile)
} else if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
if (SubSettingActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.sub_setting)
} else if (SettingsActivity::class.java.isAssignableFrom(javaClass)) {
navigationView.setCheckedItem(R.id.settings)

View File

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

View File

@@ -1,38 +1,41 @@
package com.v2ray.ang.ui
import android.Manifest
import android.content.*
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.net.Uri
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.text.TextUtils
import android.view.KeyEvent
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.V2rayConfigUtil
import org.jetbrains.anko.*
import java.lang.ref.SoftReference
import java.net.URL
import android.content.IntentFilter
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.helper.ItemTouchHelper
import android.text.TextUtils
import android.util.Log
//import com.v2ray.ang.InappBuyActivity
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.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.android.schedulers.AndroidSchedulers
import java.net.URL
import java.util.concurrent.TimeUnit
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.util.AngConfigManager.configs
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
companion object {
@@ -42,22 +45,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
private const val REQUEST_SCAN_URL = 3
}
var isRunning = false
set(value) {
field = value
adapter.changeable = !value
if (value) {
fab.imageResource = R.drawable.ic_v
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.imageResource = R.drawable.ic_v_idle
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
}
private val adapter by lazy { MainRecyclerAdapter(this) }
private var mItemTouchHelper: ItemTouchHelper? = null
private val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -66,28 +56,23 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
setSupportActionBar(toolbar)
fab.setOnClickListener {
if (isRunning) {
if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this)
} else {
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
val intent = VpnService.prepare(this)
if (intent == null) {
startV2Ray()
} else {
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
}
} else {
startV2Ray()
}
}
layout_test.setOnClickListener {
if (isRunning) {
val socksPort = 10808//Utils.parseInt(defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
if (mainViewModel.isRunning.value == true) {
tv_test_state.text = getString(R.string.connection_test_testing)
doAsync {
val result = Utils.testConnection(this@MainActivity, socksPort)
uiThread {
tv_test_state.text = Utils.getEditable(result)
}
}
mainViewModel.testCurrentServerRealPing()
} else {
// tv_test_state.text = getString(R.string.connection_test_fail)
}
@@ -107,6 +92,34 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
drawer_layout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver()
}
private fun setupViewModelObserver() {
mainViewModel.updateListAction.observe(this, {
val index = it ?: return@observe
if (index >= 0) {
adapter.updateSelectedItem(index)
} else {
adapter.updateConfigList()
}
})
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
mainViewModel.isRunning.observe(this, {
val isRunning = it ?: return@observe
adapter.changeable = !isRunning
if (isRunning) {
fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected)
} else {
fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected)
}
hideCircle()
})
mainViewModel.startListenBroadcast()
}
fun startV2Ray() {
@@ -115,32 +128,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
showCircle()
// toast(R.string.toast_services_start)
if (!Utils.startVService(this)) {
if (!Utils.startVService(this, AngConfigManager.configs.index)) {
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() {
super.onResume()
adapter.updateConfigList()
@@ -162,8 +154,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
}
REQUEST_FILE_CHOOSER -> {
if (resultCode == RESULT_OK) {
val uri = data!!.data
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
readContentFromUri(uri)
}
}
@@ -179,6 +171,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true
}
private fun getOptionIntent() = Intent().putExtra("position", -1)
.putExtra("isRunning", mainViewModel.isRunning.value == true)
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.import_qrcode -> {
importQRcode(REQUEST_SCAN)
@@ -189,17 +184,17 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
R.id.import_manually_vmess -> {
startActivity<ServerActivity>("position" to -1, "isRunning" to isRunning)
startActivity(getOptionIntent().setClass(this, ServerActivity::class.java))
adapter.updateConfigList()
true
}
R.id.import_manually_ss -> {
startActivity<Server3Activity>("position" to -1, "isRunning" to isRunning)
startActivity(getOptionIntent().setClass(this, Server3Activity::class.java))
adapter.updateConfigList()
true
}
R.id.import_manually_socks -> {
startActivity<Server4Activity>("position" to -1, "isRunning" to isRunning)
startActivity(getOptionIntent().setClass(this, Server4Activity::class.java))
adapter.updateConfigList()
true
}
@@ -240,20 +235,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.ping_all -> {
for (k in 0 until configs.vmess.count()) {
configs.vmess[k].testResult = ""
adapter.updateConfigList()
}
for (k in 0 until configs.vmess.count()) {
if (configs.vmess[k].configType != AppConfig.EConfigType.Custom) {
doAsync {
configs.vmess[k].testResult = Utils.tcping(configs.vmess[k].address, configs.vmess[k].port)
uiThread {
adapter.updateSelectedItem(k)
}
}
}
}
mainViewModel.testAllTcping()
true
}
@@ -282,7 +264,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult<ScannerActivity>(requestCode)
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
else
toast(R.string.toast_permission_denied)
}
@@ -368,9 +350,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
toast(R.string.toast_invalid_url)
return false
}
doAsync {
val configText = URL(url).readText()
uiThread {
GlobalScope.launch(Dispatchers.IO) {
val configText = try {
URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
importCustomizeConfig(configText)
}
}
@@ -402,9 +389,14 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
continue
}
Log.d("Main", url)
doAsync {
val configText = URL(url).readText()
uiThread {
GlobalScope.launch(Dispatchers.IO) {
val configText = try {
URL(url).readText()
} catch (e: Exception) {
e.printStackTrace()
""
}
launch(Dispatchers.Main) {
importBatchConfig(Utils.decode(configText), id)
}
}
@@ -442,9 +434,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.subscribe {
if (it) {
try {
val inputStream = contentResolver.openInputStream(uri)
val configText = inputStream.bufferedReader().readText()
importCustomizeConfig(configText)
contentResolver.openInputStream(uri).use {
val configText = it?.bufferedReader()?.readText()
importCustomizeConfig(configText)
}
} catch (e: Exception) {
e.printStackTrace()
}
@@ -482,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 {
if (keyCode == KeyEvent.KEYCODE_BACK) {
moveTaskToBack(false)
@@ -549,10 +513,11 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
when (item.itemId) {
//R.id.server_profile -> activityClass = MainActivity::class.java
R.id.sub_setting -> {
startActivity<SubSettingActivity>()
startActivity(Intent(this, SubSettingActivity::class.java))
}
R.id.settings -> {
startActivity<SettingsActivity>("isRunning" to isRunning)
startActivity(Intent(this, SettingsActivity::class.java)
.putExtra("isRunning", mainViewModel.isRunning.value == true))
}
R.id.feedback -> {
Utils.openUri(this, AppConfig.v2rayNGIssues)
@@ -564,10 +529,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
// startActivity<InappBuyActivity>()
}
R.id.logcat -> {
startActivity<LogcatActivity>()
startActivity(Intent(this, LogcatActivity::class.java))
}
}
drawer_layout.closeDrawer(GravityCompat.START)
return true
}
}
}

View File

@@ -1,24 +1,28 @@
package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color
import android.support.v7.app.AlertDialog
import android.support.v7.widget.RecyclerView
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import kotlinx.android.synthetic.main.item_qrcode.view.*
import kotlinx.android.synthetic.main.item_recycler_main.view.*
import org.jetbrains.anko.*
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
import com.v2ray.ang.extension.defaultDPreference
class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<MainRecyclerAdapter.BaseViewHolder>()
, ItemTouchHelperAdapter {
@@ -49,7 +53,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) {
val configType = configs.vmess[position].configType
val configType = EConfigType.fromInt(configs.vmess[position].configType)
val remarks = configs.vmess[position].remarks
val subid = configs.vmess[position].subid
val address = configs.vmess[position].address
@@ -58,7 +62,7 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.name.text = remarks
holder.radio.isChecked = (position == configs.index)
holder.itemView.backgroundColor = Color.TRANSPARENT
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = test_result
if (TextUtils.isEmpty(subid)) {
@@ -67,39 +71,33 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
holder.subid.text = "S"
}
if (configType == AppConfig.EConfigType.Vmess) {
holder.type.text = "vmess"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Custom) {
var shareOptions = share_method.asList()
if (configType == EConfigType.CUSTOM) {
holder.type.text = mActivity.getString(R.string.server_customize_config)
holder.statistics.text = ""//mActivity.getString(R.string.server_customize_config)
holder.layout_share.visibility = View.INVISIBLE
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
holder.type.text = "shadowsocks"
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
if (serverOutbound == null) {
holder.statistics.text = ""
} else {
holder.statistics.text = "${serverOutbound.getServerAddress()} : ${serverOutbound.getServerPort()}"
}
shareOptions = shareOptions.takeLast(1)
} else {
holder.type.text = configType?.name?.toLowerCase()
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
} else if (configType == AppConfig.EConfigType.Socks) {
holder.type.text = "socks"
holder.statistics.text = "$address : $port"
holder.layout_share.visibility = View.VISIBLE
}
holder.layout_share.setOnClickListener {
mActivity.selector(null, share_method.asList()) { dialogInterface, i ->
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
try {
when (i) {
0 -> {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
mActivity.alert {
customView {
linearLayout {
addView(iv)
}
}
}.show()
if (configType == EConfigType.CUSTOM) {
shareFullContent(position)
} else {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
AlertDialog.Builder(mActivity).setView(iv).show()
}
}
1 -> {
if (AngConfigManager.share2Clipboard(position) == 0) {
@@ -108,31 +106,26 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
mActivity.toast(R.string.toast_failure)
}
}
2 -> {
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
else ->
mActivity.toast("else")
2 -> shareFullContent(position)
else -> mActivity.toast("else")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}.show()
}
holder.layout_edit.setOnClickListener {
if (configType == AppConfig.EConfigType.Vmess) {
mActivity.startActivity<ServerActivity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Custom) {
mActivity.startActivity<Server2Activity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Shadowsocks) {
mActivity.startActivity<Server3Activity>("position" to position, "isRunning" to !changeable)
} else if (configType == AppConfig.EConfigType.Socks) {
mActivity.startActivity<Server4Activity>("position" to position, "isRunning" to !changeable)
val intent = Intent().putExtra("position", position)
.putExtra("isRunning", !changeable)
if (configType == EConfigType.VMESS) {
mActivity.startActivity(intent.setClass(mActivity, ServerActivity::class.java))
} else if (configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, Server2Activity::class.java))
} else if (configType == EConfigType.SHADOWSOCKS) {
mActivity.startActivity(intent.setClass(mActivity, Server3Activity::class.java))
} else if (configType == EConfigType.SOCKS) {
mActivity.startActivity(intent.setClass(mActivity, Server4Activity::class.java))
}
}
holder.layout_remove.setOnClickListener {
@@ -150,16 +143,14 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
} else {
mActivity.showCircle()
Utils.stopVService(mActivity)
AngConfigManager.setActiveServer(position)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
mActivity.showCircle()
if (!Utils.startVService(mActivity)) {
if (!Utils.startVService(mActivity, position)) {
mActivity.hideCircle()
}
}
}
notifyDataSetChanged()
}
@@ -176,13 +167,21 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
}
private fun shareFullContent(position: Int) {
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) {
VIEW_TYPE_ITEM ->
return MainViewHolder(parent.context.layoutInflater
return MainViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_main, parent, false))
else ->
return FooterViewHolder(parent.context.layoutInflater
return FooterViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_footer, parent, false))
}
}
@@ -265,4 +264,8 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
return true
}
override fun onItemMoveCompleted() {
AngConfigManager.storeConfigFile()
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,104 +1,22 @@
package com.v2ray.ang.ui
import android.content.*
import android.net.VpnService
import com.v2ray.ang.R
import com.v2ray.ang.util.Utils
import android.os.Bundle
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MessageUtil
import java.lang.ref.SoftReference
import android.content.IntentFilter
import kotlinx.android.synthetic.main.activity_main.*
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
import com.v2ray.ang.service.V2RayServiceManager
class ScSwitchActivity : BaseActivity() {
companion object {
private const val REQUEST_CODE_VPN_PREPARE = 0
}
var isRunning = false
set(value) {
field = value
if (value) {
Utils.stopVService(this)
} else {
val intent = VpnService.prepare(this)
if (intent == null) {
Utils.startVService(this)
} else {
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
}
}
finishActivity()
}
fun finishActivity() {
try {
Observable.timer(5000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
finish()
}
} catch (e: Exception) {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
moveTaskToBack(true)
setContentView(R.layout.activity_none)
val isRunning = Utils.isServiceRun(this, "com.v2ray.ang.service.V2RayVpnService")
if (isRunning) {
//Utils.stopVService(this)
mMsgReceive = ReceiveMessageHandler(this@ScSwitchActivity)
registerReceiver(mMsgReceive, IntentFilter(AppConfig.BROADCAST_ACTION_ACTIVITY))
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
if (V2RayServiceManager.v2rayPoint.isRunning) {
Utils.stopVService(this)
} else {
Utils.startVService(this)
finishActivity()
Utils.startVServiceFromToggle(this)
}
finish()
}
override fun onStop() {
super.onStop()
if (mMsgReceive != null) {
unregisterReceiver(mMsgReceive)
mMsgReceive = null
}
}
private var mMsgReceive: BroadcastReceiver? = null
private class ReceiveMessageHandler(activity: ScSwitchActivity) : BroadcastReceiver() {
internal var mReference: SoftReference<ScSwitchActivity> = SoftReference(activity)
override fun onReceive(ctx: Context?, intent: Intent?) {
val activity = mReference.get()
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING -> {
activity?.isRunning = true
}
AppConfig.MSG_STATE_NOT_RUNNING -> {
activity?.isRunning = false
}
// AppConfig.MSG_STATE_START_SUCCESS -> {
// activity?.toast(R.string.toast_services_success)
// activity?.isRunning = true
// }
// AppConfig.MSG_STATE_START_FAILURE -> {
// activity?.toast(R.string.toast_services_failure)
// activity?.isRunning = false
// }
// AppConfig.MSG_STATE_STOP_SUCCESS -> {
// activity?.isRunning = false
// }
}
}
}
}
}

View File

@@ -7,23 +7,13 @@ import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
import android.content.Intent
import android.graphics.BitmapFactory
import android.icu.util.TimeUnit
import android.view.Menu
import android.view.MenuItem
import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.QRCodeDecoder
import org.jetbrains.anko.toast
import rx.Observable
import android.os.SystemClock
import kotlinx.android.synthetic.main.activity_main.*
import rx.Observer
import rx.android.schedulers.AndroidSchedulers
import javax.xml.datatype.DatatypeConstants.SECONDS
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
companion object {
@@ -119,10 +109,10 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_FILE_CHOOSER ->
if (resultCode == RESULT_OK) {
REQUEST_FILE_CHOOSER -> {
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
try {
val uri = data!!.data
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text)
@@ -131,6 +121,7 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
toast(e.message.toString())
}
}
}
}
}
}

View File

@@ -1,6 +1,7 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.Editable
import android.text.TextUtils
import android.view.Menu
@@ -10,13 +11,12 @@ import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server2.*
import org.jetbrains.anko.*
import java.lang.Exception
class Server2Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
@@ -69,40 +69,32 @@ class Server2Activity : BaseActivity() {
* save server config
*/
fun saveServer(): Boolean {
var saveSuccess: Boolean
val vmess = configs.vmess[edit_index]
vmess.remarks = et_remarks.text.toString()
if (TextUtils.isEmpty(vmess.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 {
Gson().fromJson<Object>(tv_content.text.toString(), Object::class.java)
} catch (e: Exception) {
e.printStackTrace()
toast(R.string.toast_malformed_josn)
saveSuccess = false
return false
}
if (saveSuccess) {
if (AngConfigManager.addCustomServer(vmess, edit_index) == 0) {
//update config
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
AngConfigManager.genStoreV2rayConfigIfActive(edit_index)
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
@@ -112,17 +104,16 @@ class Server2Activity : BaseActivity() {
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
alert(R.string.del_config_comfirm) {
positiveButton(android.R.string.ok) {
if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
if (AngConfigManager.removeServer(edit_index) == 0) {
toast(R.string.toast_success)
finish()
} else {
toast(R.string.toast_failure)
}
}
}
show()
}
.show()
} else {
}
return true
@@ -158,4 +149,4 @@ class Server2Activity : BaseActivity() {
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,13 +15,11 @@ import com.v2ray.ang.AppConfig.SS_PROTOCOL
import com.v2ray.ang.AppConfig.VMESS_PROTOCOL
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.VmessQRCode
import com.v2ray.ang.extension.defaultDPreference
import org.jetbrains.anko.toast
import java.net.URI
import java.net.URLDecoder
import java.util.*
import java.net.*
import java.math.BigInteger
object AngConfigManager {
private lateinit var app: AngApplication
@@ -69,7 +67,7 @@ object AngConfigManager {
fun addServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Vmess
vmess.configType = EConfigType.VMESS.value
if (index >= 0) {
//edit
@@ -104,16 +102,7 @@ object AngConfigManager {
angConfig.vmess.removeAt(index)
//移除的是活动的
if (angConfig.index == index) {
if (angConfig.vmess.count() > 0) {
angConfig.index = 0
} else {
angConfig.index = -1
}
} else if (index < angConfig.index)//移除活动之前的
{
angConfig.index--
}
adjustIndexForRemovalAt(index)
storeConfigFile()
} catch (e: Exception) {
@@ -123,6 +112,19 @@ object AngConfigManager {
return 0
}
private fun adjustIndexForRemovalAt(index: Int) {
if (angConfig.index == index) {
if (angConfig.vmess.count() > 0) {
angConfig.index = 0
} else {
angConfig.index = -1
}
} else if (index < angConfig.index)//移除活动之前的
{
angConfig.index--
}
}
fun swapServer(fromPosition: Int, toPosition: Int): Int {
try {
Collections.swap(angConfig.vmess, fromPosition, toPosition)
@@ -133,7 +135,7 @@ object AngConfigManager {
} else if (index == toPosition) {
angConfig.index = fromPosition
}
storeConfigFile()
//storeConfigFile()
} catch (e: Exception) {
e.printStackTrace()
return -1
@@ -153,6 +155,10 @@ object AngConfigManager {
angConfig.index = index
app.curIndex = index
storeConfigFile()
if (!genStoreV2rayConfig()) {
Log.d(AppConfig.ANG_PACKAGE, "set active index $index but generate full configuration failed!")
return -1
}
} catch (e: Exception) {
e.printStackTrace()
app.curIndex = -1
@@ -173,49 +179,46 @@ 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
*/
fun genStoreV2rayConfig(index: Int): Boolean {
fun genStoreV2rayConfig(): Boolean {
try {
if (angConfig.index < 0
|| angConfig.vmess.count() <= 0
|| angConfig.index > angConfig.vmess.count() - 1
) {
return false
}
var index2 = angConfig.index
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
angConfig.vmess.getOrNull(angConfig.index)?.let {
val result = V2rayConfigUtil.getV2rayConfig(app, it)
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
}
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
return false
}
fun currGeneratedV2rayConfig(): String {
return app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
}
fun currConfigType(): Int {
fun currConfigType(): EConfigType? {
if (angConfig.index < 0
|| angConfig.vmess.count() <= 0
|| angConfig.index > angConfig.vmess.count() - 1
) {
return -1
return null
}
return angConfig.vmess[angConfig.index].configType
return EConfigType.fromInt(angConfig.vmess[angConfig.index].configType)
}
fun currConfigName(): String {
@@ -241,7 +244,7 @@ object AngConfigManager {
/**
* import config form qrcode or...
*/
fun importConfig(server: String?, subid: String): Int {
fun importConfig(server: String?, subid: String, removedSelectedServer: AngConfig.VmessBean?): Int {
try {
if (server == null || TextUtils.isEmpty(server)) {
return R.string.toast_none_data
@@ -252,7 +255,11 @@ object AngConfigManager {
if (server.startsWith(VMESS_PROTOCOL)) {
val indexSplit = server.indexOf("?")
if (indexSplit > 0) {
val newVmess = tryParseNewVmess(server)
if (newVmess != null) {
vmess = newVmess
vmess.subid = subid
} else if (indexSplit > 0) {
vmess = ResolveVmess4Kitsunebi(server)
} else {
@@ -271,7 +278,7 @@ object AngConfigManager {
return R.string.toast_incorrect_protocol
}
vmess.configType = AppConfig.EConfigType.Vmess
vmess.configType = EConfigType.VMESS.value
vmess.security = "auto"
vmess.network = "tcp"
vmess.headerType = "none"
@@ -313,7 +320,7 @@ object AngConfigManager {
result = Utils.decode(result)
}
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".toRegex()
val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)/?$".toRegex()
val match = legacyPattern.matchEntire(result)
if (match == null) {
return R.string.toast_incorrect_protocol
@@ -361,6 +368,12 @@ object AngConfigManager {
} else {
return R.string.toast_incorrect_protocol
}
if (removedSelectedServer != null &&
vmess.subid.equals(removedSelectedServer.subid) &&
vmess.address.equals(removedSelectedServer.address) &&
vmess.port.equals(removedSelectedServer.port)) {
setActiveServer(configs.vmess.count() - 1)
}
} catch (e: Exception) {
e.printStackTrace()
return -1
@@ -368,6 +381,61 @@ object AngConfigManager {
return 0
}
fun tryParseNewVmess(uri: String): AngConfig.VmessBean? {
return runCatching {
val uri = URI(uri)
check(uri.scheme == "vmess")
val (_, protocol, tlsStr, uuid, alterId) =
Regex("(tcp|http|ws|kcp|quic)(\\+tls)?:([0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12})-([0-9]+)")
.matchEntire(uri.userInfo)?.groupValues
?: error("parse user info fail.")
val tls = tlsStr.isNotBlank()
val queryParam = uri.rawQuery.split("&")
.map { it.split("=").let { (k, v) -> k to URLDecoder.decode(v, "utf-8")!! } }
.toMap()
val vmess = AngConfig.VmessBean()
vmess.address = uri.host
vmess.port = uri.port
vmess.id = uuid
vmess.alterId = alterId.toInt()
vmess.streamSecurity = if (tls) "tls" else ""
vmess.remarks = uri.fragment
vmess.security = "auto"
// TODO: allowInsecure not supported
when (protocol) {
"tcp" -> {
vmess.network = "tcp"
vmess.headerType = queryParam["type"] ?: "none"
vmess.requestHost = queryParam["host"] ?: ""
}
"http" -> {
vmess.network = "h2"
vmess.path = queryParam["path"]?.takeIf { it.trim() != "/" } ?: ""
vmess.requestHost = queryParam["host"]?.split("|")?.get(0) ?: ""
}
"ws" -> {
vmess.network = "ws"
vmess.path = queryParam["path"]?.takeIf { it.trim() != "/" } ?: ""
vmess.requestHost = queryParam["host"]?.split("|")?.get(0) ?: ""
}
"kcp" -> {
vmess.network = "kcp"
vmess.headerType = queryParam["type"] ?: "none"
vmess.path = queryParam["seed"] ?: ""
}
"quic" -> {
vmess.network = "quic"
vmess.requestHost = queryParam["security"] ?: "none"
vmess.headerType = queryParam["type"] ?: "none"
vmess.path = queryParam["key"] ?: ""
}
}
vmess
}.getOrNull()
}
private fun ResolveVmess4Kitsunebi(server: String): AngConfig.VmessBean {
val vmess = AngConfig.VmessBean()
@@ -413,7 +481,7 @@ object AngConfigManager {
}
val vmess = angConfig.vmess[index]
if (angConfig.vmess[index].configType == AppConfig.EConfigType.Vmess) {
if (angConfig.vmess[index].configType == EConfigType.VMESS.value) {
val vmessQRCode = VmessQRCode()
vmessQRCode.v = vmess.configVersion.toString()
@@ -431,7 +499,7 @@ object AngConfigManager {
val conf = VMESS_PROTOCOL + Utils.encode(json)
return conf
} else if (angConfig.vmess[index].configType == AppConfig.EConfigType.Shadowsocks) {
} else if (angConfig.vmess[index].configType == EConfigType.SHADOWSOCKS.value) {
val remark = "#" + Utils.urlEncode(vmess.remarks)
val url = String.format("%s:%s@%s:%s",
vmess.security,
@@ -439,7 +507,7 @@ object AngConfigManager {
vmess.address,
vmess.port)
return SS_PROTOCOL + Utils.encode(url) + remark
} else if (angConfig.vmess[index].configType == AppConfig.EConfigType.Socks) {
} else if (angConfig.vmess[index].configType == EConfigType.SOCKS.value) {
val remark = "#" + Utils.urlEncode(vmess.remarks)
val url = String.format("%s:%s",
vmess.address,
@@ -520,9 +588,9 @@ object AngConfigManager {
*/
fun shareFullContent2Clipboard(index: Int): Int {
try {
if (AngConfigManager.genStoreV2rayConfig(index)) {
val configContent = app.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
Utils.setClipboard(app.applicationContext, configContent)
val result = V2rayConfigUtil.getV2rayConfig(app, angConfig.vmess[index])
if (result.status) {
Utils.setClipboard(app.applicationContext, result.content)
} else {
return -1
}
@@ -548,7 +616,7 @@ object AngConfigManager {
//add
val vmess = AngConfig.VmessBean()
vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Custom
vmess.configType = EConfigType.CUSTOM.value
vmess.guid = guid
vmess.remarks = vmess.guid
@@ -651,7 +719,7 @@ object AngConfigManager {
fun addCustomServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Custom
vmess.configType = EConfigType.CUSTOM.value
if (index >= 0) {
//edit
@@ -676,7 +744,7 @@ object AngConfigManager {
fun addShadowsocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Shadowsocks
vmess.configType = EConfigType.SHADOWSOCKS.value
if (index >= 0) {
//edit
@@ -701,7 +769,7 @@ object AngConfigManager {
fun addSocksServer(vmess: AngConfig.VmessBean, index: Int): Int {
try {
vmess.configVersion = 2
vmess.configType = AppConfig.EConfigType.Socks
vmess.configType = EConfigType.SOCKS.value
if (index >= 0) {
//edit
@@ -728,6 +796,17 @@ object AngConfigManager {
if (servers == null) {
return 0
}
val removedSelectedServer =
if (!TextUtils.isEmpty(subid)) {
configs.vmess.getOrNull(configs.index)?.let {
if (it.subid == subid) {
return@let it
}
return@let null
}
} else {
null
}
removeServerViaSubid(subid)
// var servers = server
@@ -738,7 +817,7 @@ object AngConfigManager {
var count = 0
servers.lines()
.forEach {
val resId = importConfig(it, subid)
val resId = importConfig(it, subid, removedSelectedServer)
if (resId == 0) {
count++
}
@@ -778,6 +857,7 @@ object AngConfigManager {
for (k in configs.vmess.count() - 1 downTo 0) {
if (configs.vmess[k].subid.equals(subid)) {
angConfig.vmess.removeAt(k)
adjustIndexForRemovalAt(k)
}
}
@@ -823,4 +903,4 @@ object AngConfigManager {
return 0
}
}
}

View File

@@ -16,7 +16,7 @@ object AppManagerUtil {
val apps = ArrayList<AppInfo>()
for (pkg in packages) {
if (!pkg.hasInternetPermission) continue
if (!pkg.hasInternetPermission && pkg.packageName != "android") continue
val applicationInfo = pkg.applicationInfo
@@ -40,4 +40,4 @@ object AppManagerUtil {
val permissions = requestedPermissions
return permissions?.any { it == Manifest.permission.INTERNET } ?: false
}
}
}

View File

@@ -11,43 +11,33 @@ import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.EncodeHintType
import java.util.*
import kotlin.collections.HashMap
import android.app.ActivityManager
import android.content.ClipData
import android.content.Intent
import android.content.res.AssetManager
import android.net.Uri
import android.os.SystemClock
import android.text.TextUtils
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.util.Patterns
import android.view.View
import android.webkit.URLUtil
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.extension.responseLength
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.service.V2RayVpnService
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.ui.SettingsActivity
import kotlinx.android.synthetic.main.activity_logcat.*
import kotlinx.coroutines.isActive
import me.dozen.dpreference.DPreference
import org.jetbrains.anko.toast
import org.jetbrains.anko.uiThread
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.net.*
import java.util.regex.Matcher
import java.util.regex.Pattern
import java.math.BigInteger
import java.util.concurrent.TimeUnit
import libv2ray.Libv2ray
import kotlin.coroutines.coroutineContext
object Utils {
val tcpTestingSockets = ArrayList<Socket?>()
/**
* convert string to editalbe for kotlin
*
@@ -102,7 +92,7 @@ object Utils {
try {
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(null, content)
cmb.primaryClip = clipData
cmb.setPrimaryClip(clipData)
} catch (e: Exception) {
e.printStackTrace()
}
@@ -280,59 +270,14 @@ object Utils {
return false
}
/**
* 判断服务是否后台运行
* @param context
* * Context
* *
* @param className
* * 判断的服务名字
* *
* @return true 在运行 false 不在运行
*/
fun isServiceRun(context: Context, className: String): Boolean {
var isRun = false
val activityManager = context
.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val serviceList = activityManager
.getRunningServices(999)
val size = serviceList.size
for (i in 0..size - 1) {
if (serviceList[i].service.className == className) {
isRun = true
break
}
}
return isRun
}
/**
* 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 == AppConfig.EConfigType.Custom) {
try {
Libv2ray.testConfig(configContent)
} catch (e: Exception) {
context.toast(e.toString())
return false
}
}
V2RayVpnService.startV2Ray(context)
return true
} else {
fun startVServiceFromToggle(context: Context): Boolean {
val result = context.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG, "")
if (result.isBlank()) {
context.toast(R.string.app_tile_first_use)
return false
}
V2RayServiceManager.startV2Ray(context)
return true
}
/**
@@ -348,8 +293,11 @@ object Utils {
* startVService
*/
fun startVService(context: Context, index: Int): Boolean {
AngConfigManager.setActiveServer(index)
return startVService(context)
if (AngConfigManager.setActiveServer(index) < 0) {
return false
}
V2RayServiceManager.startV2Ray(context)
return true
}
/**
@@ -448,7 +396,6 @@ object Utils {
return path
}
/**
* readTextFromAssets
*/
@@ -483,10 +430,13 @@ object Utils {
/**
* tcping
*/
fun tcping(url: String, port: Int): String {
suspend fun tcping(url: String, port: Int): String {
var time = -1L
for (k in 0 until 2) {
val one = socketConnectTime(url, port)
if (!coroutineContext.isActive) {
break
}
if (one != -1L )
if(time == -1L || one < time) {
time = one
@@ -497,19 +447,35 @@ object Utils {
fun socketConnectTime(url: String, port: Int): Long {
try {
val socket = Socket()
synchronized(this) {
tcpTestingSockets.add(socket)
}
val start = System.currentTimeMillis()
val socket = Socket(url, port)
socket.connect(InetSocketAddress(url, port))
val time = System.currentTimeMillis() - start
synchronized(this) {
tcpTestingSockets.remove(socket)
}
socket.close()
return time
} catch (e: UnknownHostException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
Log.d(AppConfig.ANG_PACKAGE, "socketConnectTime IOException: $e")
} catch (e: Exception) {
e.printStackTrace()
}
return -1
}
fun closeAllTcpSockets() {
synchronized(this) {
tcpTestingSockets.forEach {
it?.close()
}
tcpTestingSockets.clear()
}
}
}

View File

@@ -1,20 +1,21 @@
package com.v2ray.ang.util
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonArray
import com.google.gson.*
import com.v2ray.ang.AngApplication
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AngConfig.VmessBean
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.ui.SettingsActivity
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONArray
import com.google.gson.JsonObject
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashSet
object V2rayConfigUtil {
private val requestObj: JsonObject by lazy {
@@ -41,19 +42,10 @@ object V2rayConfigUtil {
// return result
// }
if (vmess.configType == AppConfig.EConfigType.Vmess) {
result = getV2rayConfigType1(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Custom) {
if (vmess.configType == EConfigType.CUSTOM.value) {
result = getV2rayConfigType2(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Shadowsocks) {
} else {
result = getV2rayConfigType1(app, vmess)
} else if (vmess.configType == AppConfig.EConfigType.Socks) {
result = getV2rayConfigType1(app, vmess)
}
val domainName = parseDomainName(result.content)
if (!TextUtils.isEmpty(domainName)) {
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
}
Log.d("V2rayConfigUtilGoLog", result.content)
@@ -64,6 +56,27 @@ 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: V2rayConfig? = try {
Gson().fromJson(jsonConfig, V2rayConfig::class.java)
} catch (e: JsonSyntaxException) {
e.printStackTrace()
null
}
v2rayConfig?.outbounds?.forEach { outbound ->
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
outbound.protocol.equals(EConfigType.SOCKS.name, true)) {
return outbound
}
}
return null
}
/**
* 生成v2ray的客户端配置文件
*/
@@ -116,6 +129,7 @@ object V2rayConfigUtil {
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
result.status = true
result.content = jsonConfig
parseDomainNameAndTag(app, jsonConfig)
return result
} catch (e: Exception) {
@@ -164,8 +178,12 @@ object V2rayConfigUtil {
try {
val outbound = v2rayConfig.outbounds[0]
when (vmess.configType) {
AppConfig.EConfigType.Vmess -> {
val configType = EConfigType.fromInt(vmess.configType)
if (configType != null) {
outbound.protocol = configType.name.toLowerCase()
}
when (configType) {
EConfigType.VMESS -> {
outbound.settings?.servers = null
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
@@ -183,10 +201,8 @@ object V2rayConfigUtil {
//远程服务器底层传输配置
outbound.streamSettings = boundStreamSettings(vmess)
outbound.protocol = "vmess"
}
AppConfig.EConfigType.Shadowsocks -> {
EConfigType.SHADOWSOCKS -> {
outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0)
@@ -199,10 +215,8 @@ object V2rayConfigUtil {
//Mux
outbound.mux?.enabled = false
outbound.protocol = "shadowsocks"
}
AppConfig.EConfigType.Socks -> {
EConfigType.SOCKS -> {
outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0)
@@ -211,21 +225,22 @@ object V2rayConfigUtil {
//Mux
outbound.mux?.enabled = false
outbound.protocol = "socks"
}
else -> {
}
}
var serverDomain: String
if(Utils.isIpv6Address(vmess.address)) {
serverDomain = String.format("[%s]:%s", vmess.address, vmess.port)
val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
String.format("[%s]:%s", vmess.address, vmess.port)
} else {
serverDomain = String.format("%s:%s", vmess.address, vmess.port)
String.format("%s:%s", vmess.address, vmess.port)
}
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, serverDomain)
val tags = LinkedHashSet<String>()
v2rayConfig.outbounds.forEach {
if (!TextUtils.isEmpty(it.tag)) {
tags.add(it.tag)
}
}
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
} catch (e: Exception) {
e.printStackTrace()
return false
@@ -246,7 +261,7 @@ object V2rayConfigUtil {
//streamSettings
when (streamSettings.network) {
"kcp" -> {
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean()
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
kcpsettings.mtu = 1350
kcpsettings.tti = 50
kcpsettings.uplinkCapacity = 12
@@ -254,34 +269,33 @@ object V2rayConfigUtil {
kcpsettings.congestion = false
kcpsettings.readBufferSize = 1
kcpsettings.writeBufferSize = 1
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpsettingsBean.HeaderBean()
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
kcpsettings.header.type = vmess.headerType
streamSettings.kcpsettings = kcpsettings
streamSettings.kcpSettings = kcpsettings
}
"ws" -> {
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean()
wssettings.connectionReuse = true
val wssettings = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
if (!TextUtils.isEmpty(host)) {
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WssettingsBean.HeadersBean()
wssettings.headers = V2rayConfig.OutboundBean.StreamSettingsBean.WsSettingsBean.HeadersBean()
wssettings.headers.Host = host
}
if (!TextUtils.isEmpty(path)) {
wssettings.path = path
}
streamSettings.wssettings = wssettings
streamSettings.wsSettings = wssettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean()
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true
if (!TextUtils.isEmpty(host)) {
tlssettings.serverName = host
}
streamSettings.tlssettings = tlssettings
streamSettings.tlsSettings = tlssettings
}
"h2" -> {
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpsettingsBean()
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
@@ -289,31 +303,30 @@ object V2rayConfigUtil {
httpsettings.host = host.split(",").map { it.trim() }
}
httpsettings.path = path
streamSettings.httpsettings = httpsettings
streamSettings.httpSettings = httpsettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlssettingsBean()
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true
streamSettings.tlssettings = tlssettings
streamSettings.tlsSettings = tlssettings
}
"quic" -> {
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean()
val quicsettings = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
quicsettings.security = host
quicsettings.key = path
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicsettingBean.HeaderBean()
quicsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.QuicSettingBean.HeaderBean()
quicsettings.header.type = vmess.headerType
streamSettings.quicsettings = quicsettings
streamSettings.quicSettings = quicsettings
}
else -> {
//tcp带http伪装
if (vmess.headerType == "http") {
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean()
tcpSettings.connectionReuse = true
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpsettingsBean.HeaderBean()
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
tcpSettings.header.type = vmess.headerType
// if (requestObj.has("headers")
@@ -619,42 +632,58 @@ object V2rayConfigUtil {
}
}
private fun parseDomainName(jsonConfig: String): String {
private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
try {
val jObj = JSONObject(jsonConfig)
var domainName: String
var domainName = ""
val tags = LinkedHashSet<String>()
if (jObj.has("outbound")) {
domainName = parseDomainName(jObj.optJSONObject("outbound"))
if (!TextUtils.isEmpty(domainName)) {
return domainName
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
domainName = domain
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
}
}
if (jObj.has("outbounds")) {
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
domainName = parseDomainName(jObj.optJSONArray("outbounds").getJSONObject(i))
if (!TextUtils.isEmpty(domainName)) {
return domainName
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outbounds").getJSONObject(i))
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
domainName = domain
}
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
}
}
}
if (jObj.has("outboundDetour")) {
for (i in 0..(jObj.optJSONArray("outboundDetour").length() - 1)) {
domainName = parseDomainName(jObj.optJSONArray("outboundDetour").getJSONObject(i))
if (!TextUtils.isEmpty(domainName)) {
return domainName
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONArray("outboundDetour").getJSONObject(i))
if (!TextUtils.isEmpty(domain) && TextUtils.isEmpty(domainName)) {
domainName = domain
}
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
}
}
}
if (!TextUtils.isEmpty(domainName)) {
app.defaultDPreference.setPrefString(AppConfig.PREF_CURR_CONFIG_DOMAIN, domainName)
}
app.defaultDPreference.setPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, tags)
} catch (e: Exception) {
e.printStackTrace()
}
return ""
}
private fun parseDomainName(outbound: JSONObject): String {
private fun parseDomainNameAndTag(outbound: JSONObject): Pair<String, String> {
val tag = if (outbound.has("tag")) {
outbound.getString("tag")
} else {
""
}
try {
if (outbound.has("settings")) {
var vnext: JSONArray?
val vnext: JSONArray?
if (outbound.optJSONObject("settings").has("vnext")) {
// vmess
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
@@ -662,22 +691,22 @@ object V2rayConfigUtil {
// shadowsocks or socks
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
} else {
return ""
return Pair("", tag)
}
for (i in 0..(vnext.length() - 1)) {
val item = vnext.getJSONObject(i)
val address = item.getString("address")
val port = item.getString("port")
if(Utils.isIpv6Address(address)) {
return String.format("[%s]:%s", address, port)
return Pair(String.format("[%s]:%s", address, port), tag)
} else {
return String.format("%s:%s", address, port)
return Pair(String.format("%s:%s", address, port), tag)
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return ""
return Pair("", tag)
}
}
}

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

@@ -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>

View File

@@ -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

View File

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

View File

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

View File

@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="@+id/layout_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -14,16 +7,9 @@
android:gravity="center"
android:orientation="vertical">
<ImageView
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@mipmap/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/app_widget_name"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
android:padding="10dp"
android:src="@drawable/ic_v" />
</LinearLayout>

View File

@@ -4,12 +4,7 @@
tools:showIn="navigation_view">
<group
android:id="@+id/group_main"
android:checkableBehavior="single">
<item
android:id="@+id/server_profile"
android:icon="@drawable/ic_description_white_24dp"
android:title="@string/title_server" />
android:id="@+id/group_main">
<item
android:id="@+id/sub_setting"
android:icon="@drawable/ic_subscriptions_white_24dp"
@@ -21,25 +16,27 @@
</group>
<item android:title="@string/title_about">
<menu>
<item
<group android:id="@+id/group_id2">
<item
android:id="@+id/promotion"
android:icon="@drawable/ic_whatshot_white_24dp"
android:title="@string/title_pref_promotion" />
<item
<item
android:id="@+id/donate"
android:icon="@drawable/ic_attach_money_white_24dp"
android:title="@string/title_pref_donate" />
<item
<item
android:id="@+id/logcat"
android:icon="@drawable/ic_logcat_white_24dp"
android:title="@string/title_logcat" />
<item
<item
android:id="@+id/feedback"
android:icon="@drawable/ic_feedback_white_24dp"
android:title="@string/title_pref_feedback" />
</menu>
</item>
<!-- place holder for version text at the bottom -->
<item
android:id="@+id/placeholder"
android:enabled="false"
android:title="" />
</group>
</menu>

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<string name="app_name">v2rayNG</string>
<string name="app_widget_name">开关</string>
<string name="app_tile_name">开关</string>
<string name="app_tile_first_use">初次使用此功能请先用APP激活VPN</string>
<string name="app_tile_first_use">初次使用此功能请先用APP添加配置</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
@@ -73,7 +73,6 @@
<!-- Preferences -->
<string name="title_settings">设置</string>
<string name="title_about">关于</string>
<string name="title_advanced">进阶设置</string>
<string name="title_pref_per_app_proxy">分应用代理</string>
@@ -83,7 +82,7 @@
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
<string name="title_pref_speed_enabled">启用速度显示</string>
<string name="summary_pref_speed_enabled">在通知中显示当前速度</string>
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
<string name="title_pref_sniffing_enabled">启用流量探测</string>
<string name="summary_pref_sniffing_enabled">流量探测</string>
@@ -123,7 +122,8 @@
<string name="title_pref_promotion">推广</string>
<string name="summary_pref_promotion">一些推广,点击查看详情(捐赠可去除)</string>
<string name="title_pref_version">版本</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">点此查看更多帮助</string>
<string name="donate_error_setup">初始化错误:</string>
<string name="donate_error_inventory">无法查询到项目</string>
@@ -136,7 +136,7 @@
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">复制</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="sub_setting_remarks">备注</string>
<string name="sub_setting_url">地址(url)</string>
@@ -187,4 +187,9 @@
<string name="toast_warning_pref_proxysharing_short">代理共享已启用,请确保处于受信网络</string>
<string name="toast_malformed_josn">配置格式错误</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>仅代理</item>
</string-array>
</resources>

View File

@@ -3,7 +3,7 @@
<string name="app_name">v2rayNG</string>
<string name="app_widget_name">切換</string>
<string name="app_tile_name">切換</string>
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式來啟用 VPN</string>
<string name="app_tile_first_use">首次使用此功能,請使用此應用程式新增組態</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
@@ -74,7 +74,6 @@
<!-- Preferences -->
<string name="title_settings">設定</string>
<string name="title_about">關於</string>
<string name="title_advanced">進階設定</string>
<string name="title_pref_per_app_proxy">Proxy 個別應用程式</string>
@@ -84,7 +83,7 @@
<string name="summary_pref_mux_enabled">啟用或許會加快網路速度,切換或許會閃爍</string>
<string name="title_pref_speed_enabled">啟用速度顯示</string>
<string name="summary_pref_speed_enabled">在通知中顯示當前速度</string>
<string name="summary_pref_speed_enabled">在通知中顯示當前速度\n小圖標顯示流量的路由情況</string>
<string name="title_pref_sniffing_enabled">啟用流量探測</string>
<string name="summary_pref_sniffing_enabled">流量探測</string>
@@ -125,7 +124,8 @@
<string name="title_pref_promotion">推廣</string>
<string name="summary_pref_promotion">一些推廣,點擊查看詳情(捐款可去除)</string>
<string name="title_pref_version">版本</string>
<string name="title_mode">模式</string>
<string name="title_mode_help">點此查看更多幫助</string>
<string name="donate_error_setup">錯誤設定:</string>
<string name="donate_error_inventory">Error querying inventory</string>
@@ -138,7 +138,7 @@
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">複製</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="sub_setting_remarks">備註</string>
<string name="sub_setting_url">位址(url)</string>
@@ -188,4 +188,10 @@
<string name="toast_warning_pref_proxysharing">其他設備可以使用socks/http協定通過您的IP地址連接到代理\nHttp 代理: http://您的ip:10809\nSocks 代理: socks(4/5)://您的ip:10808\n僅在受信任的網路中啟用以避免未經授權的連接</string>
<string name="toast_warning_pref_proxysharing_short">代理共享已啟用,請確保處於受信網路</string>
<string name="toast_malformed_josn">配置格式錯誤</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>僅代理</item>
</string-array>
</resources>

View File

@@ -66,4 +66,44 @@
<item>IPIfNonMatch</item>
<item>IPOnDemand</item>
</string-array>
<string-array name="mode_value" translatable="false">
<item>VPN</item>
<item>Proxy only</item>
</string-array>
<!-- minimum list https://serverfault.com/a/304791 -->
<string-array name="bypass_private_ip_address" translatable="false">
<item>0.0.0.0/5</item>
<item>8.0.0.0/7</item>
<item>11.0.0.0/8</item>
<item>12.0.0.0/6</item>
<item>16.0.0.0/4</item>
<item>32.0.0.0/3</item>
<item>64.0.0.0/2</item>
<item>128.0.0.0/3</item>
<item>160.0.0.0/5</item>
<item>168.0.0.0/6</item>
<item>172.0.0.0/12</item>
<item>172.32.0.0/11</item>
<item>172.64.0.0/10</item>
<item>172.128.0.0/9</item>
<item>173.0.0.0/8</item>
<item>174.0.0.0/7</item>
<item>176.0.0.0/4</item>
<item>192.0.0.0/9</item>
<item>192.128.0.0/11</item>
<item>192.160.0.0/13</item>
<item>192.169.0.0/16</item>
<item>192.170.0.0/15</item>
<item>192.172.0.0/14</item>
<item>192.176.0.0/12</item>
<item>192.192.0.0/10</item>
<item>193.0.0.0/8</item>
<item>194.0.0.0/7</item>
<item>196.0.0.0/6</item>
<item>200.0.0.0/5</item>
<item>208.0.0.0/4</item>
<item>224.0.0.0/3</item>
</string-array>
</resources>

View File

@@ -3,7 +3,7 @@
<string name="app_name">v2rayNG</string>
<string name="app_widget_name">Switch</string>
<string name="app_tile_name">Switch</string>
<string name="app_tile_first_use">First use of this feature, please use the app to activate VPN</string>
<string name="app_tile_first_use">First use of this feature, please use the app to add server</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
@@ -41,7 +41,7 @@
<string name="server_lab_network">network</string>
<string name="server_lab_more_function">more function</string>
<string name="server_lab_head_type">head type</string>
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC securty</string>
<string name="server_lab_request_host">request host(host/ws host/h2 host)/QUIC security</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC key</string>
<string name="server_lab_stream_security">tls</string>
<string name="server_lab_allow_insecure">allowInsecure</string>
@@ -74,7 +74,6 @@
<!-- Preferences -->
<string name="title_settings">Settings</string>
<string name="title_about">About</string>
<string name="title_advanced">Advanced Settings</string>
<string name="title_pref_per_app_proxy">Per-app proxy</string>
@@ -84,7 +83,8 @@
<string name="summary_pref_mux_enabled">Enable maybe speed up network and switch network maybe flash</string>
<string name="title_pref_speed_enabled">Enable speed display</string>
<string name="summary_pref_speed_enabled">Display current speed in the notification</string>
<string name="summary_pref_speed_enabled">Display current speed in the notification.\nNotification icon would change based on
usage.</string>
<string name="title_pref_sniffing_enabled">Enable Sniffing</string>
<string name="summary_pref_sniffing_enabled">Sniffing</string>
@@ -124,7 +124,8 @@
<string name="title_pref_promotion">Promotion</string>
<string name="summary_pref_promotion">Promotion,click for details(Donation can be removed)</string>
<string name="title_pref_version">Version</string>
<string name="title_mode">Mode</string>
<string name="title_mode_help">Click me for more help</string>
<string name="donate_error_setup">Error Setup:</string>
<string name="donate_error_inventory">Error querying inventory</string>
@@ -137,7 +138,7 @@
<string name="title_logcat">Logcat</string>
<string name="logcat_copy">Copy</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="sub_setting_remarks">remarks</string>
<string name="sub_setting_url">url</string>
@@ -188,4 +189,9 @@
<string name="toast_warning_pref_proxysharing_short">Proxy sharing enabled\nMake sure you are in a trusted network</string>
<string name="toast_malformed_josn">Config malformed</string>
<string-array name="mode_entries">
<item>VPN</item>
<item>Proxy only</item>
</string-array>
</resources>

View File

@@ -13,6 +13,12 @@
<item name="windowNoTitle">true</item>
</style>
<style name="AppTheme.NoActionBar.Translucent">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion Integer.parseInt("$compileSdkVer")
buildToolsVersion buildToolsVer
defaultConfig {
minSdkVersion 17
@@ -19,7 +19,7 @@ android {
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.12'
testImplementation 'junit:junit:4.13'
implementation "com.android.support:support-annotations:$supportLibVersion"
implementation 'com.google.code.gson:gson:2.7'
implementation 'com.google.code.gson:gson:2.8.6'
}

View File

@@ -2,7 +2,9 @@ package me.dozen.dpreference;
import android.content.Context;
import android.text.TextUtils;
import java.util.LinkedHashSet;
import java.util.Set;
/**
@@ -45,10 +47,6 @@ public class DPreference {
PrefAccessor.setInt(mContext, mName, key, value);
}
public void setPrefStringSet(final String key, final Set<String> value) {
PrefAccessor.setStringSet(mContext, mName, key, value);
}
public int getPrefInt(final String key, final int defaultValue) {
return PrefAccessor.getInt(mContext, mName, key, defaultValue);
}
@@ -61,10 +59,26 @@ public class DPreference {
return PrefAccessor.getLong(mContext, mName, key, defaultValue);
}
public void setPrefStringSet(final String key, final Set<String> value) {
PrefAccessor.setStringSet(mContext, mName, key, value);
}
public Set<String> getPrefStringSet(final String key, final Set<String> defaultValue) {
return PrefAccessor.getStringSet(mContext, mName, key, defaultValue);
}
public void setPrefStringOrderedSet(final String key, final LinkedHashSet<String> value) {
PrefAccessor.setString(mContext, mName, key, StringSetConverter.encode(value));
}
public LinkedHashSet<String> getPrefStringOrderedSet(final String key, final LinkedHashSet<String> defaultValue) {
String value = PrefAccessor.getString(mContext, mName, key, "");
if (TextUtils.isEmpty(value)) {
return defaultValue;
}
return StringSetConverter.decode(value);
}
public void removePreference(final String key) {
PrefAccessor.remove(mContext, mName, key);
}

View File

@@ -4,6 +4,7 @@ import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.LinkedHashSet;
import java.util.Set;
public class StringSetConverter {
@@ -13,8 +14,8 @@ public class StringSetConverter {
return gson.toJson(src);
}
public static Set<String> decode(String json) {
Type setType = new TypeToken<Set<String>>() {
public static LinkedHashSet<String> decode(String json) {
Type setType = new TypeToken<LinkedHashSet<String>>() {
}.getType();
return gson.fromJson(json, setType);
}

View File

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

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