Compare commits

...

130 Commits

Author SHA1 Message Date
2dust
8ed17f9da0 Merge pull request #1446 from pozhiloy-enotik/patch-1
Allow external use
2022-04-15 07:21:23 +08:00
pozhiloy-enotik
1bbfda64fe Allow external use 2022-04-12 16:18:34 +03:00
2dust
5cadef8b2a Merge pull request #1416 from yuhan6665/master
Refactor and remove Kotlin synthetics
2022-03-20 10:16:16 +08:00
yuhan6665
5b92158353 Update readme 2022-03-19 21:27:26 -04:00
yuhan6665
73706c1d0f Refactor and remove Kotlin synthetics 2022-03-19 21:21:44 -04:00
2dust
c633a267ff Merge pull request #1393 from yuhan6665/master
Update readme and sync project
2022-02-27 08:55:34 +08:00
yuhan6665
c8e5bf4f9f Update project 2022-02-26 12:15:46 -05:00
2dust
bb91b3baa9 onModeHelpClicked 2022-02-26 12:05:02 -05:00
2dust
35dc8d661c Update AndroidManifest.xml 2022-02-26 12:05:02 -05:00
2dust
f61f30fdc4 change sdk version 2022-02-26 12:05:02 -05:00
2dust
a54c327a07 Merge pull request #1374 from Kaitul/patch-1
Update strings.xml
2022-02-26 19:21:22 +08:00
人工知能
87d2854fb2 Update strings.xml 2022-02-15 12:48:44 +08:00
2dust
e9b1052ef7 Merge pull request #1336 from yuhan6665/remove-alterid
Remove alterid
2022-01-09 18:04:08 +08:00
yuhan6665
c6560e9bc0 Remove alterId field
VmessQRCode "aid" is kept for backwards compatibility and always set to 0
Since 2022, new *ray server will reject alterId > 0.
If it is enabled and set specifically > 0, for v2fly server v4.28.1+ and Xray server v1.0.0+, client with either 0 or above 0 can connect
For older v2ray server, the default config will not work, user has to use custom config to set alterId. This is NOT recommended for security reason
2022-01-07 18:30:11 -05:00
yuhan6665
a44ca16aa7 Fix some compile warnings 2022-01-07 18:30:11 -05:00
2dust
b28c7f54ff Merge pull request #1333 from xiandanin/master
更新xray-core版本
2022-01-06 19:37:50 +08:00
xiandanin
61a155b799 更新xray-core版本 2022-01-05 20:25:45 +08:00
2dust
10cc117e81 Merge pull request #1268 from yuhan6665/new-fakedns
New config format
2021-10-30 19:02:26 +08:00
yuhan6665
ce9bed2e1f New config format 2021-10-29 20:37:12 -04:00
2dust
bb8f7de6eb Merge pull request #1263 from yuhan6665/new-dns
Add support for DOT and DOQ
2021-10-24 19:51:47 +08:00
yuhan6665
a1ed4836c7 Add support for DOT and DOQ 2021-10-23 20:24:08 -04:00
2dust
2d5351ec9e Merge pull request #1218 from yuhan6665/fix-sniffing2
Fix sniffing default value again
2021-09-07 20:02:52 +08:00
yuhan6665
d9fb121d67 Fix sniffing default value again
I made a mistake, thought it would return null when key is not in storage,
But it is found default "true" must be specified
2021-09-03 18:15:06 -04:00
2dust
62d0951a24 Merge pull request #1208 from yuhan6665/fix-viewpager
Fix an issue with ViewPage2 and menu bar
2021-08-28 17:06:21 +08:00
2dust
d6605cc866 Merge pull request #1204 from douo/master
Add support in subscription url for http basic authentication.
2021-08-28 17:06:10 +08:00
yuhan6665
1fc493d879 Fix an issue with ViewPage2 and menu bar 2021-08-27 17:48:45 -04:00
tiou
bdc27dd180 Use built-in urlDecode. 2021-08-23 16:58:48 +08:00
tiou
e257c4cb56 Subscription support http basic authentication in url. 2021-08-23 16:49:35 +08:00
2dust
e9d2ed98af Merge pull request #1198 from yuhan6665/user-agent
Set user agent to v2rayNG/version when update subscription
2021-08-21 19:19:20 +08:00
yuhan6665
901a82eb54 Set user agent to v2rayNG/version when update subscription 2021-08-20 18:29:34 -04:00
2dust
26cc29944b Merge pull request #1183 from yuhan6665/fix-drag
Fix a bug when user delete item right after drag it
2021-08-17 20:20:56 +08:00
yuhan6665
7052546f2b Fix a bug when user delete item right after drag it 2021-08-13 18:04:43 -04:00
2dust
ae2b10ede7 Merge pull request #1177 from yuhan6665/fix-base64
Fix parsing for some loosely formatted base64
2021-08-10 13:09:35 +08:00
2dust
0df051e640 Merge pull request #1175 from jiuqianyuan/master
Add prompts for update subscription error
2021-08-10 13:06:14 +08:00
jiuqianyuan
79aa86a402 Update MainActivity.kt 2021-08-09 17:22:49 +08:00
yuhan6665
7bf32c2b30 Fix parsing for some loosely formatted base64 2021-08-07 17:35:09 -04:00
jiuqianyuan
ceb1c55e49 Fix missing prompts for update subscription error 2021-08-07 08:08:45 +08:00
2dust
4ab3134eac Merge pull request #1164 from yuhan6665/update
Fix compile warnings
2021-07-31 15:38:19 +08:00
yuhan6665
0391685d42 Fix some more compile warnings 2021-07-30 18:31:29 -04:00
yuhan6665
6482253697 Fix some compile warnings
remove flatDir
2021-07-30 18:31:29 -04:00
yuhan6665
c033358126 Fix some compile warnings
Migrate to Androidx Activity Result APIs
2021-07-30 18:31:29 -04:00
yuhan6665
0b52c78b72 Fix some compile warnings
Some deprecated methods
2021-07-30 18:31:29 -04:00
yuhan6665
a2a7d790e7 Fix some compile warnings
Migrate to viewpager2
2021-07-30 18:31:29 -04:00
yuhan6665
c7c6564624 Fix some compile warnings
upper case and lower case
2021-07-30 18:31:29 -04:00
2dust
f7d534fc00 Merge pull request #1153 from yuhan6665/update
Migrate to View Binding
2021-07-26 08:07:28 +08:00
yuhan6665
ecb1d58e7a Migrate to View Binding
Use view binding to simplify some code
2021-07-23 18:17:17 -04:00
2dust
54a191d181 Merge pull request #1147 from yuhan6665/update
Update project dependencies
2021-07-20 08:12:30 +08:00
2dust
90b6979f94 Merge pull request #1146 from jiuqianyuan/master
fix update subscription error
2021-07-20 08:12:18 +08:00
jiuqianyuan
5daf4886b7 fix wrong commit
wrong commit for dbee208
2021-07-17 16:18:33 +08:00
jiuqianyuan
dbee2085fb clean useless “” 2021-07-17 15:58:29 +08:00
yuhan6665
3c6c4ca3a5 Update project dependencies 2021-07-16 18:11:28 -04:00
jiuqianyuan
3932ee6e9b fix update subscription error
Prevent server list from being emptied when update subscription failed or the subscription url is invalid
2021-07-16 17:49:56 +08:00
2dust
242c96a4de Merge pull request #1136 from yuhan6665/EditorKit
Editor kit
2021-07-14 08:26:14 +08:00
yuhan6665
a23245435f Use EditorKit for custom config
https://github.com/massivemadness/Squircle-IDE
2021-07-09 20:45:48 -04:00
yuhan6665
57476290d3 Auto Migrate to AndroidX 2021-07-09 20:44:38 -04:00
2dust
c5617ac65a Merge pull request #1129 from yuhan6665/fix-host
Fix host should be empty list when it is blank
2021-07-03 16:48:12 +08:00
yuhan6665
3795348b04 Fix host should be empty list when it is blank 2021-07-02 17:40:15 -04:00
2dust
50bf9c8da0 Merge pull request #1116 from yuhan6665/fix-base64
Fix base64-url-safe format decode
2021-06-27 11:52:20 +08:00
yuhan6665
78fe4e4bc4 Fix base64-url-safe format decode 2021-06-26 19:44:21 -04:00
2dust
962519854d Merge pull request #1092 from yuhan6665/Logtag
Log with consistent tag
2021-06-13 19:53:46 +08:00
yuhan6665
841629f9a3 Cleanup some files 2021-06-12 23:38:31 -04:00
yuhan6665
d32ccc817a Log with consistent tag 2021-06-12 23:38:31 -04:00
2dust
19cc665f2d Merge pull request #1088 from yuhan6665/import-fix
Fix a null pointer when subscribe link doesn't have sni field
2021-06-07 08:17:13 +08:00
yuhan6665
031e9105e2 Fix a null pointer when subscribe link doesn't have sni field 2021-06-06 11:18:58 -04:00
2dust
35c5d64863 Merge pull request #1070 from yuhan6665/bypass-fix
bypass local also bypass multicast address
2021-05-29 10:29:25 +08:00
yuhan6665
0b05756d12 bypass local also bypass multicast address
https://www.iana.org/assignments/ipv4-address-space/ipv4-address-space.xhtml
2021-05-28 19:51:08 -04:00
2dust
3548dcbb67 Merge pull request #1065 from yuhan6665/domestic
Always add domestic dns to config
2021-05-24 08:14:43 +08:00
yuhan6665
b897b2a0e9 Always add domestic dns to config
Previously, domestic dns is only added to config under
"local dns mode". However, it should be used for V2ray
core routing DNS as well.
2021-05-23 11:56:23 -04:00
2dust
eb60e4a0e4 Merge pull request #1058 from yuhan6665/grpc
Grpc
2021-05-16 19:35:16 +08:00
yuhan6665
c3dfa8cedc Support gRPC import and export
New Format https://github.com/XTLS/Xray-core/issues/91
For Vmess QRcode https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2)
gRPC mode is mapped to "type"
2021-05-15 23:18:09 -04:00
yuhan6665
f58bf85b6d Update UI for gRPC
Separate transport header types for different networks
Change UI dynamically based on user selection of network
2021-05-15 23:04:58 -04:00
2dust
906d0714b5 Merge pull request #1050 from yuhan6665/fakedns
Fakedns
2021-05-08 11:28:42 +08:00
yuhan6665
701fed2525 Update settings UI, add new VPN section 2021-05-07 21:55:44 -04:00
yuhan6665
e83208465f Add vpn dns preference setting
Vpn Dns can now be changed separately from remote Dns
2021-05-07 21:17:11 -04:00
yuhan6665
2b031033d3 Add fake dns preference setting 2021-05-07 20:55:02 -04:00
2dust
e0e16b5934 fix tcp http request 2021-05-07 20:50:52 -04:00
2dust
0748f994ef Merge pull request #1038 from yuhan6665/new-storage-fix
New storage fix
2021-05-01 18:20:11 +08:00
yuhan6665
233b34bda6 Fix scrolling by cache server config in ViewModel 2021-04-30 20:17:41 -04:00
yuhan6665
6ece3385fe Fix trojan sni saved as null when it should be empty 2021-04-30 20:17:41 -04:00
2dust
9b8a810445 Merge pull request #1028 from yuhan6665/master
Update tun2socks
2021-04-26 08:22:47 +08:00
yuhan6665
f63242d147 Update tun2socks
60a66a363f
compiled with NDK 22.1.7171670
2021-04-25 19:56:14 -04:00
2dust
7024fabb82 Merge pull request #1016 from yuhan6665/new-storage-fix
Fix data migration sniff setting default to true
2021-04-17 10:24:19 +08:00
2dust
459f52fec6 Merge pull request #1015 from yuhan6665/xray
Update AndroidLibrayLite project to Xray
2021-04-17 10:24:10 +08:00
yuhan6665
90f2d33d97 Fix data migration sniff setting default to true 2021-04-16 20:33:59 -04:00
yuhan6665
115008a8a4 Update AndroidLibrayLite project to Xray 2021-04-16 09:17:58 -04:00
2dust
8cdc7fb3c9 Merge pull request #1001 from yuhan6665/new-storage-fix
Rollback parsed custom config
2021-04-11 19:28:47 +08:00
yuhan6665
d0a2fa0086 Rollback parsed custom config
To minimize change, the data structure for ServerConfig still stay the same
Add another table for server raw config
2021-04-10 21:30:34 -04:00
2dust
f6c54841d2 Merge pull request #990 from yuhan6665/new-storage-fix
New storage fix
2021-04-05 20:21:51 +08:00
yuhan6665
54fa356999 Add some new config for v2fly 4.37.0 2021-04-04 19:25:52 -04:00
2dust
9642b7f64f up grpc 2021-04-04 19:25:52 -04:00
yuhan6665
dd2d2c1638 Add some missing config from Xray 2021-04-04 19:25:51 -04:00
yuhan6665
e21950dbcd Improve custom config error toast 2021-04-04 19:25:51 -04:00
2dust
d016ab06d4 Merge pull request #973 from yuhan6665/new-storage-fix
New storage fix
2021-03-29 08:06:43 +08:00
yuhan6665
4d9aced5a4 Gson display to not escape HTML character like "=" 2021-03-28 11:34:41 -04:00
yuhan6665
62b928e6a0 Support Trojan flow and email 2021-03-28 11:34:34 -04:00
2dust
0ce60eae73 Update MainRecyclerAdapter.kt 2021-03-28 11:34:12 -04:00
2dust
5930a6a9eb Update V2rayConfig.kt 2021-03-28 11:34:01 -04:00
2dust
a360310be2 fix header type none 2021-03-28 11:33:51 -04:00
2dust
820e6cdf36 fix migration inbound port parsed as 0 2021-03-28 11:31:18 -04:00
2dust
658b890325 Merge pull request #965 from yuhan6665/new-storage
New storage
2021-03-27 09:42:12 +08:00
yuhan6665
fb017c6659 Add migration
- Fix some compile warnings
- Add migration method
- Add vmessBean migration
2021-03-26 20:55:00 -04:00
yuhan6665
00e6314afe Merge branch 'master' into new-storage 2021-03-26 20:55:00 -04:00
yuhan6665
463f45804f Add missing data fields for config
Make custom config able to parse any config
Fix subscription TODO
Fix some bugs
- vless tls should clear out flow
- trojan tls settings
- import with null rawQuery link
- socks share link username
- remove an item in the middle of server list
2021-03-26 20:55:00 -04:00
yuhan6665
572955dd1e Move everything else to MMKV
- Exclude old armv5 so lib from MMKV
- Move subscription to MMKV
- Clean up settings key
- Map settings to MMKV
- Say goodbye to DPreference

Fix some bugs:
- null pointer check
- server list refresh
- show test result
- custom config display
- fix port number in DNS object shown as Double
- shadowsocks, socks streamsettings
- quic settings with wrong security
- main list footer
2021-03-20 01:00:12 -04:00
yuhan6665
375a209beb Move main storage references to MMKV
- Move current index to MMKV
- Move ServiceManager to MMKV
- Fix isRunning in ServerActivity
- Drop support for VMESS qrcode v1
- Change all protocols importing
2021-03-19 23:23:10 -04:00
yuhan6665
872f9ce199 Add MMKV and data class
- Move ServerActivity to MMKV
- Move ServerCustomConfigActivity to MMKV
- Cleanup ServerActivities
- Cleanup V2rayConfigUtil
- Change data reads in RecyclerAdapter and ConfigManager
- Cleanup some constants
2021-03-19 23:23:09 -04:00
2dust
b4f02c9bd6 Merge pull request #894 from Humilton/master
Support keyboard navigation for fab
2021-02-21 12:56:05 +08:00
hubaozhong
e567719f5b Support keyboard navigation for fab
Signed-off-by: hubaozhong <d63hbz@gmail.com>
2021-02-18 23:01:31 +08:00
2dust
8407fc5825 Merge pull request #866 from yuhan6665/trim-r
Fix the string list trim
2021-02-01 08:05:37 +08:00
yuhan6665
a3e49dcc3d Fix the string list trim 2021-01-30 21:59:54 -05:00
2dust
7b47bbe99a Merge pull request #846 from yuhan6665/list-ui
Add subscription remarks in server list
2021-01-16 13:18:27 +08:00
yuhan6665
0fb2165015 Add subscription remarks in server list 2021-01-15 20:44:28 -05:00
2dust
03eeeb9b62 Merge pull request #827 from yuhan6665/master
Update readme
2021-01-02 09:18:45 +08:00
yuhan6665
038daf5fda Update readme 2021-01-01 09:21:10 -05:00
2dust
bfd1387d9b Merge pull request #817 from yuhan6665/fix-manual-update
Fix manual update
2020-12-26 12:03:38 +08:00
yuhan6665
5afec5cf25 Update full config when edit manually 2020-12-25 18:34:58 -05:00
yuhan6665
ec29bdf5bf Refactor for re-use genStoreV2rayConfig() 2020-12-25 18:34:58 -05:00
2dust
57efab093f Merge pull request #803 from yuhan6665/fix-712
Fix toast BadTokenException in OS 7.1.2
2020-12-14 08:21:51 +08:00
2dust
9c92a64811 Merge pull request #801 from yuhan6665/fix-oculus
Fix widget manager null pointer in Oculus
2020-12-14 08:21:41 +08:00
yuhan6665
7ddc82d5cd Fix toast BadTokenException in OS 7.1.2
Apparently recent changes with ViewModel affect the internal of
Activity which lead to toast throwing BadTokenException in OS
7.1.2.
The error is not easily catchable. This library use reflection
to override a key function in WindowManager to catch the error.
I have audit the code of the library.

See https://github.com/PureWriter/ToastCompat for more details
2020-12-13 18:45:04 -05:00
yuhan6665
c286ba18a8 Fix widget manager null pointer in Oculus 2020-12-12 23:11:00 -05:00
2dust
867b5fc880 Merge pull request #795 from rurirei/v2init
init update
2020-12-10 08:25:10 +08:00
rurirei
e8a7fa5320 create init 2020-12-09 17:35:09 +08:00
rurirei
f2f9e55286 do not init all 2020-12-09 17:32:50 +08:00
2dust
4a1c62a67c Merge pull request #787 from yuhan6665/fix-selected
Fix update full config when settings change
2020-12-06 13:48:03 +08:00
yuhan6665
c9a6a459d4 Fix update full config when settings change
Now daemon process does not reference the node list at all and
only depend on a couple of settings like PREF_CURR_CONFIG..
2020-12-05 23:42:44 -05:00
2dust
21fdcf4ccf Merge pull request #783 from yuhan6665/fix-selected
Fix select node of two processes in sync
2020-12-05 18:20:11 +08:00
yuhan6665
7c7a623ae5 Fix select node of two processes in sync
As proxy only mode is added in 1.4.0, I moved the toggle components to the daemon process
When user start service from toggle, the full config is generated from a cached list.
However, this cache is not in sync with the main process list.
In fact, we don't need to generate full config right before the service start. We only
need to when active node is changed.
This way, code logic and daemon process is kept simple
2020-12-04 22:37:31 -05:00
2dust
b3074e9697 Merge pull request #763 from yuhan6665/memory-optimization
Slightly improve memory by reduce unnecessary DPreference usage
2020-11-29 09:31:54 +08:00
yuhan6665
513ebcfa23 Slightly improve memory by reduce unnecessary DPreference usage
See more details in _Ext.kt.
In the future, change will be made to our config storage, so that
the service started through TileService/Widget/ScSwitchActivity will
also not launch main process. That will greatly reduce memory usage
2020-11-28 18:08:50 -05:00
122 changed files with 3719 additions and 7050 deletions

2
AndroidLibV2rayLite/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.jar
*.aar

View File

@@ -1,26 +0,0 @@
sudo: required
language: go
go:
- "1.12"
go_import_path: github.com/2dust/AndroidLibV2rayLite
git:
depth: 5
addons:
apt:
update: true
before_script:
- sudo ntpdate -u time.google.com
- date
- make all
- make downloadGoMobile
script:
- make BuildMobile
after_success:
deploy:
provider: releases
api_key: ${GH_TOKEN}
file:
- libv2ray.aar
skip_cleanup: true
on:
tags: true

View File

@@ -1,7 +1,7 @@
package CoreI
import (
v2core "v2ray.com/core"
v2core "github.com/xtls/xray-core/core"
)
type Status struct {
@@ -14,7 +14,7 @@ type Status struct {
}
func CheckVersion() int {
return 22
return 23
}
func (v *Status) GetDataDir() string {

View File

@@ -8,24 +8,19 @@ 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
fetchDep:
-go get github.com/2dust/AndroidLibV2rayLite
go get github.com/2dust/AndroidLibV2rayLite
ANDROID_HOME=$(HOME)/android-sdk-linux
export ANDROID_HOME
PATH:=$(PATH):$(GOPATH)/bin
export PATH
downloadGoMobile:
go get golang.org/x/mobile/cmd/...
sudo apt-get install -qq libstdc++6:i386 lib32z1 expect
cd ~ ;curl -L https://raw.githubusercontent.com/2dust/AndroidLibV2rayLite/master/ubuntu-cli-install-android-sdk.sh | sudo bash - > /dev/null
ls ~
ls ~/android-sdk-linux/
gomobile init ;gomobile bind -v -tags json github.com/2dust/AndroidLibV2rayLite
BuildMobile:
@echo Stub
gomobile init
gomobile bind -v -ldflags='-s -w' github.com/2dust/AndroidLibV2rayLite
all: asset pb fetchDep
all: asset pb
@echo DONE

View File

@@ -1 +1,16 @@
# AndroidLibV2rayLite
进入`AndroidLibV2rayLite`文件夹
```
go mod download
```
编译aar
```
gomobile bind -v -o android.aar -target=android ./
```

View File

@@ -12,8 +12,10 @@ import (
"time"
"golang.org/x/sys/unix"
v2net "v2ray.com/core/common/net"
v2internet "v2ray.com/core/transport/internet"
v2net "github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/features/dns"
"github.com/xtls/xray-core/features/outbound"
v2internet "github.com/xtls/xray-core/transport/internet"
)
type protectSet interface {
@@ -190,6 +192,11 @@ func (d *ProtectedDialer) getFd(network v2net.Network) (fd int, err error) {
return
}
// Init implement internet.SystemDialer
func (d *ProtectedDialer) Init(_ dns.Client, _ outbound.Manager) {
// do nothing
}
// Dial exported as the protected dial method
func (d *ProtectedDialer) Dial(ctx context.Context,
src v2net.Address, dest v2net.Destination, sockopt *v2internet.SocketConfig) (net.Conn, error) {

View File

@@ -9,7 +9,7 @@ import (
"testing"
"time"
v2net "v2ray.com/core/common/net"
v2net "github.com/xtls/xray-core/common/net"
)
type fakeSupportSet struct{}

4
AndroidLibV2rayLite/compile-tun2socks.sh Normal file → Executable file
View File

@@ -23,8 +23,8 @@ trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp
install -m644 $__dir/tun2socks.mk $TMPDIR/
pushd $TMPDIR
git clone --depth=1 https://github.com/shadowsocks/badvpn.git
git clone --depth=1 https://github.com/shadowsocks/libancillary.git
git clone --depth=1 https://github.com/XTLS/badvpn.git
git clone --depth=1 https://github.com/XTLS/libancillary.git
$NDK_HOME/ndk-build \
NDK_PROJECT_PATH=. \
APP_BUILD_SCRIPT=./tun2socks.mk \

View File

@@ -0,0 +1,9 @@
module github.com/2dust/AndroidLibV2rayLite
go 1.16
require (
github.com/xtls/xray-core v1.5.2
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d
)

535
AndroidLibV2rayLite/go.sum Normal file
View File

@@ -0,0 +1,535 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.0 h1:69FNAINiZfsEuwH3fKq8QrAAnHz+2m4XL4kVYi5BX0Q=
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3 h1:hJiie5Bf3QucGRa4ymsAUOxyhYwGEz1xrsVk0P8erlw=
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0 h1:SPOUaucgtVls75mg+X7CXigS71EnsfVUK/2CgVrwqgw=
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412 h1:GvWw74lx5noHocd+f6HBMXK6DuggBB1dhVkuGZbv7qM=
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c h1:ivON6cwHK1OH26MZyWDCnbTRZZf0IhNsENoNAKFS1g4=
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999 h1:OR8VhtwhcAI3U48/rzBsVOuHi0zDPzYI1xASVcdSgR8=
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625 h1:ckJgFhFWywOx+YLEMIJsTb+NV6NexWICk5+AMSuz3ss=
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 h1:D21IyuvjDCshj1/qq+pCNd3VZOAEI9jy6Bi131YlXgI=
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d h1:t5Wuyh53qYyg9eqn4BbnlIT+vmhyww0TatL+zT3uWgI=
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-metro v0.0.0-20200812162917-85c65e2d0165/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dgryski/go-metro v0.0.0-20211015221634-2661b20a2446 h1:QnWGyQI3H080vbC9E4jlr6scOYEnALtvV/69oATYzOo=
github.com/dgryski/go-metro v0.0.0-20211015221634-2661b20a2446/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021 h1:fP+fF0up6oPY49OrjPrhIJ8yQfdIM85NXMLkMg1EXVs=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7 h1:2hRPrmiwPrp3fQX967rNJIhQPtiGXdlQWAxKbKw3VHA=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57 h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1 h1:ujPKutqRlJtcfWk6toYVYagwra7HQHbXOaS171b4Tg8=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.3 h1:/Um6a/ZmD5tF7peoOJ5oN5KMQ0DrGVQSXLNwyckutPk=
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucas-clemente/quic-go v0.24.0 h1:ToR7SIIEdrgOhgVTHvPgdVRJfgVy+N0wQAagH7L4d5g=
github.com/lucas-clemente/quic-go v0.24.0/go.mod h1:paZuzjXCE5mj6sikVLMvqXk8lJV2AsqtJ6bDhjEfxx0=
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe h1:W/GaMY0y69G4cFlmsC6B9sbuo2fP8OFP1ABjt4kPz+w=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-15 v0.1.4 h1:RehYMOyRW8hPVEja1KBVsFVNSm35Jj9Mvs5yNoZZ28A=
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I=
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco=
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk=
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI=
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/refraction-networking/utls v1.0.0 h1:6XQHSjDmeBCF9sPq8p2zMVGq7Ud3rTD2q88Fw8Tz1tA=
github.com/refraction-networking/utls v1.0.0/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c h1:pqy40B3MQWYrza7YZXOXgl0Nf0QGFqrOC0BKae1UNAA=
github.com/seiflotfy/cuckoofilter v0.0.0-20201222105146-bc6005554a0c/go.mod h1:bR6DqgcAl1zTcOX8/pE2Qkj9XO00eCNqmKb7lXP8EAg=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4 h1:Fth6mevc5rX7glNLpbAMJnqKlfIkcTjZCSHEeqvKbcI=
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48 h1:vabduItPAIz9px5iryD5peyx7O3Ya8TBThapgXim98o=
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470 h1:qb9IthCFBmROJ6YBS31BEMeSYjOscSiG+EO+JVNTz64=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d h1:Yoy/IzG4lULT6qZg62sVC+qyBL8DQkmD2zv6i7OImrc=
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c h1:UOk+nlt1BJtTcH15CT7iNO7YVWTfTv/DNwEAQHLIaDQ=
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b h1:vYEG87HxbU6dXj5npkeulCS96Dtz5xg3jcfCgpcvbIw=
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20 h1:7pDq9pAMCQgRohFmd25X8hIH8VxmT3TaDm+r9LHxgBk=
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9 h1:MPblCbqA5+z6XARjScMfz1TqtJC7TuTRj0U9VqIBs6k=
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50 h1:crYRwvwjdVh1biHzzciFHe8DrZcYrVcZFlJtykhRctg=
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc h1:eHRtZoIi6n9Wo1uR+RU44C247msLWwyA89hVKwRLkMk=
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo=
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9 h1:fxoFD0in0/CBzXoyNhMTjvBZYW6ilSnTw7N7y/8vkmM=
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191 h1:T4wuULTrzCKMFlg3HmKHgXAF8oStFb/+lOIupLV2v+o=
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241 h1:Y+TeIabU8sJD10Qwd/zMty2/LEaT9GNDaA6nyZf+jgo=
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122 h1:TQVQrsyNaimGwF7bIhzoVC9QkKm4KsWd8cECGzFx8gI=
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2 h1:bu666BQci+y4S0tVRVjsHUeRon6vUXmsGBwdowgMrg4=
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82 h1:LneqU9PHDsg/AkPDU3AkqMxnMYL+imaqkpflHu73us8=
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537 h1:YGaxtkYjb8mnTvtufv2LKLwCQu2/C7qFB7UtrOlTWOY=
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133 h1:JtcyT0rk/9PKOdnKQzuDR+FSjh7SGtJwpgVpfZBRKlQ=
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/viant/assertly v0.4.8 h1:5x1GzBaRteIwTr5RAGFVG14uNeRFxVNbXPWrK2qAgpc=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0 h1:6TteTDQ68CjgcCe8wH3D3ZhUQQOJXMTbj/D9rkk2a1k=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672 h1:4mkzGhKqt3JO1BWYjtD3iRFyAx4ow67hmSqOcGjuxqQ=
github.com/xtls/go v0.0.0-20210920065950-d4af136d3672/go.mod h1:YGGVbz9cOxyKFUmhW7LGaLZaMA0cPlHJinvAmVxEMSU=
github.com/xtls/xray-core v1.5.2 h1:gaA3vHwXdfF5y8q++l/l94fgHzx5Yj2anonDG/16t3A=
github.com/xtls/xray-core v1.5.2/go.mod h1:mqmSR72VHg0JUCnF8DWnAOTTrmnmrAYR8NWS+Cyg+F4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opencensus.io v0.18.0 h1:Mk5rgZcggtbvtAun5aJzAtjKKN/t0R3jJPlWILlv938=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.starlark.net v0.0.0-20211203141949-70c0e40ae128 h1:bxH+EXOo87zEOwKDdZ8Tevgi6irRbqheRm/fr293c58=
go.starlark.net v0.0.0-20211203141949-70c0e40ae128/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
go4.org v0.0.0-20180809161055-417644f6feb5 h1:+hE86LblG4AyDgwMCLTE6FOlM9+qjHSYS+rKqxUVdsM=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d h1:E2M5QgjZ/Jg+ObCQAudsXxuTsLj7Nl5RV/lZcQZmKSo=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b h1:QAqMVf3pSa6eeTsuklijukjXBlj7Es2QQplab+/RbQ4=
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08 h1:h+GZ3ubjuWaQjGe8owMGcmMVCqs0xYJtRG5y2bpHaqU=
golang.org/x/mobile v0.0.0-20210220033013-bdb1ca9a1e08/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852 h1:xYq6+9AtI+xP3M4r0N1hCkHrInHDBohhquRgx9Kk6gI=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d h1:1oIt9o40TWWI9FUaveVpUvBe13FNqBNVXy3ue2fcfkw=
golang.org/x/sys v0.0.0-20211214234402-4825e8c3871d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.1.0 h1:K6z2u68e86TPdSdefXdzvXgR1zEMa+459vBSfWYAZkI=
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919 h1:tmXTu+dfa+d9Evp8NpJdgOy6+rt8/x4yG7qPBrtNfLY=
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sourcegraph.com/sourcegraph/go-diff v0.5.0 h1:eTiIR0CoWjGzJcnQ3OkhIl/b9GJovq4lSAVRt0ZFEG8=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View File

@@ -13,19 +13,19 @@ import (
"github.com/2dust/AndroidLibV2rayLite/VPN"
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"
v2core "github.com/xtls/xray-core/core"
v2filesystem "github.com/xtls/xray-core/common/platform/filesystem"
v2stats "github.com/xtls/xray-core/features/stats"
v2serial "github.com/xtls/xray-core/infra/conf/serial"
_ "github.com/xtls/xray-core/main/distro/all"
v2internet "github.com/xtls/xray-core/transport/internet"
v2applog "v2ray.com/core/app/log"
v2commlog "v2ray.com/core/common/log"
v2applog "github.com/xtls/xray-core/app/log"
v2commlog "github.com/xtls/xray-core/common/log"
)
const (
v2Assert = "v2ray.location.asset"
v2Assert = "xray.location.asset"
assetperfix = "/dev/libv2rayfs0/asset"
)

View File

@@ -7,7 +7,7 @@ import (
"log"
"os"
v2commlog "v2ray.com/core/common/log"
v2commlog "github.com/xtls/xray-core/common/log"
)
type consoleLogWriter struct {

View File

@@ -1,12 +1,13 @@
# v2rayNG
A V2Ray client for Android
A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-core) and [v2fly core](https://github.com/v2fly/v2ray-core)
[![API](https://img.shields.io/badge/API-17%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/jelly-bean#android-4.2)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.4.10-blue.svg)](https://kotlinlang.org)
[![API](https://img.shields.io/badge/API-21%2B-yellow.svg?style=flat)](https://developer.android.com/about/versions/lollipop)
[![Kotlin Version](https://img.shields.io/badge/Kotlin-1.6.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" />
@@ -15,15 +16,12 @@ A V2Ray client for Android
### 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)
- geoip.dat and geosite.dat files are in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
- download feature will get enhanced version in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (Note it need a working proxy)
- latest official [domain list](https://github.com/v2fly/domain-list-community) and [ip list](https://github.com/v2fly/geoip) can be imported manually
- 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)
### More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
### Development guide
@@ -31,4 +29,5 @@ Android project under V2rayNG folder can be compiled directly in Android Studio,
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
v2rayNG can run on Android Emulators. For WSA, VPN permission need to be granted via
`appops set [package name] ACTIVATE_VPN allow`

View File

@@ -1,6 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion Integer.parseInt("$compileSdkVer")
@@ -13,7 +12,7 @@ android {
defaultConfig {
applicationId "com.v2ray.ang"
minSdkVersion 17
minSdkVersion 21
targetSdkVersion Integer.parseInt("$targetSdkVer")
multiDexEnabled true
versionCode 212
@@ -25,12 +24,14 @@ android {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
minifyEnabled false
zipAlignEnabled false
shrinkResources false
ndk.abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
}
}
@@ -57,38 +58,45 @@ android {
android.applicationVariants.all { variant ->
// assign different version code for each output
variant.outputs.each { output ->
output.versionCodeOverride =
output.outputFileName = "v2rayNG_" + variant.versionName + "_" + output.getFilter(com.android.build.OutputFile.ABI) + ".apk"
output.versionCodeOverride =
project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) *
1000000 + android.defaultConfig.versionCode
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
testImplementation 'junit:junit:4.13'
implementation project(':dpreference')
implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: [])
testImplementation 'junit:junit:4.13.2'
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
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation "androidx.legacy:legacy-support-v4:1.0.0"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "com.google.android.material:material:1.5.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation "androidx.preference:preference:1.0.0"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
// Androidx ktx
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:livedata:1.1.1'
implementation 'androidx.activity:activity-ktx:1.4.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1'
// Android support library
implementation "com.android.support:support-v4:$supportLibVersion"
implementation "com.android.support:appcompat-v7:$supportLibVersion"
implementation "com.android.support:design:$supportLibVersion"
implementation "com.android.support:cardview-v7:$supportLibVersion"
implementation "com.android.support:preference-v7:$supportLibVersion"
implementation "com.android.support:recyclerview-v7:$supportLibVersion"
implementation "com.android.support:multidex:1.0.3"
implementation 'com.android.support.constraint:constraint-layout:2.0.1'
//kotlin
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2"
// DSL
implementation 'com.tencent:mmkv-static:1.2.12'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'io.reactivex:rxjava:1.3.4'
implementation 'io.reactivex:rxandroid:1.2.1'
@@ -96,23 +104,18 @@ dependencies {
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(name: 'libv2ray', ext: 'aar')
//implementation(name: 'tun2socks', ext: 'aar')
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'com.blacksquircle.ui:editorkit:2.1.1'
implementation 'com.blacksquircle.ui:language-base:2.1.1'
implementation 'com.blacksquircle.ui:language-json:2.1.1'
}
buildscript {
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.google.com' }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlinVersion"
}
}
repositories {
flatDir {
dirs 'libs'
maven { url 'https://jitpack.io' }
jcenter()
}
}

View File

@@ -34,9 +34,11 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:extractNativeLibs="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:targetApi="m">
<activity
android:exported="true"
android:name=".ui.MainActivity"
android:theme="@style/AppTheme.NoActionBar"
android:launchMode="singleTask">
@@ -57,30 +59,40 @@
android:resource="@xml/shortcuts" />
</activity>
<activity
android:exported="false"
android:name=".ui.ServerActivity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.Server2Activity"
android:exported="false"
android:name=".ui.ServerCustomConfigActivity"
android:windowSoftInputMode="stateUnchanged" />
<activity
android:name=".ui.Server3Activity"
android:windowSoftInputMode="stateUnchanged" />
android:exported="false"
android:name=".ui.SettingsActivity" />
<activity
android:name=".ui.Server4Activity"
android:windowSoftInputMode="stateUnchanged" />
<activity android:name=".ui.SettingsActivity" />
<activity android:name=".ui.PerAppProxyActivity" />
<activity android:name=".ui.ScannerActivity" />
<!-- <activity android:name=".InappBuyActivity" />-->
<activity android:name=".ui.LogcatActivity" />
android:exported="false"
android:name=".ui.PerAppProxyActivity" />
<activity
android:exported="false"
android:name=".ui.ScannerActivity" />
<activity
android:exported="false"
android:name=".ui.LogcatActivity" />
<activity
android:exported="false"
android:name=".ui.RoutingSettingsActivity"
android:windowSoftInputMode="stateUnchanged" />
<activity android:name=".ui.SubSettingActivity" />
<activity android:name=".ui.SubEditActivity" />
<activity android:name=".ui.ScScannerActivity" />
<activity
android:exported="false"
android:name=".ui.SubSettingActivity" />
<activity
android:exported="false"
android:name=".ui.SubEditActivity" />
<activity
android:exported="false"
android:name=".ui.ScScannerActivity" />
<activity
android:exported="false"
android:name=".ui.ScSwitchActivity"
android:excludeFromRecents="true"
android:process=":RunSoLibV2RayDaemon"
@@ -107,7 +119,9 @@
android:process=":RunSoLibV2RayDaemon">
</service>
<receiver android:name=".receiver.WidgetProvider"
<receiver
android:exported="true"
android:name=".receiver.WidgetProvider"
android:process=":RunSoLibV2RayDaemon">
<meta-data
android:name="android.appwidget.provider"
@@ -120,6 +134,7 @@
</receiver>
<service
android:exported="true"
android:name=".service.QSTileService"
android:icon="@drawable/ic_v"
android:label="@string/app_tile_name"
@@ -131,6 +146,7 @@
</service>
<!-- =====================Tasker===================== -->
<activity
android:exported="true"
android:name=".ui.TaskerActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
@@ -139,7 +155,9 @@
</intent-filter>
</activity>
<receiver android:name=".receiver.TaskerReceiver"
<receiver
android:exported="true"
android:name=".receiver.TaskerReceiver"
android:process=":RunSoLibV2RayDaemon">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING" />

View File

@@ -54,7 +54,6 @@
"users": [
{
"id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
"alterId": 64,
"security": "auto",
"level": 8
}

View File

@@ -16,8 +16,8 @@
package com.v2ray.ang.helper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
/**
* Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.

View File

@@ -16,7 +16,7 @@
package com.v2ray.ang.helper;
import android.support.v7.widget.helper.ItemTouchHelper;
import androidx.recyclerview.widget.ItemTouchHelper;
/**
* Interface to notify an item ViewHolder of relevant callbacks from {@link

View File

@@ -16,7 +16,7 @@
package com.v2ray.ang.helper;
import android.support.v7.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView;
/**
* Listener for manual initiation of a drag.

View File

@@ -17,9 +17,11 @@
package com.v2ray.ang.helper;
import android.graphics.Canvas;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
import org.jetbrains.annotations.NotNull;
/**
* An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
@@ -52,7 +54,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
}
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
public int getMovementFlags(RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
// Set movement flags based on the layout manager
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
@@ -66,24 +68,25 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
public boolean onMove(@NotNull RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
if (source.getItemViewType() != target.getItemViewType()) {
return false;
}
// Notify the adapter of the move
mAdapter.onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
mAdapter.onItemMove(source.getBindingAdapterPosition(), target.getBindingAdapterPosition());
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
// Notify the adapter of the dismissal
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
mAdapter.onItemDismiss(viewHolder.getBindingAdapterPosition());
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
public void onChildDraw(@NotNull Canvas c, @NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder, float dX,
float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// Fade out the view as it is swiped out of the parent's bounds
final float alpha = ALPHA_FULL - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
@@ -109,7 +112,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
}
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
public void clearView(@NotNull RecyclerView recyclerView, @NotNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
mAdapter.onItemMoveCompleted();

View File

@@ -1,121 +0,0 @@
package com.v2ray.ang.util;
import static android.content.Context.MODE_PRIVATE;
import android.content.Context;
import android.content.res.AssetManager;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
public class AssetsUtil {
public static boolean copyAssetFolder(AssetManager assetManager,
String fromAssetPath, String toPath) {
try {
String[] files = assetManager.list(fromAssetPath);
new File(toPath).mkdirs();
boolean res = true;
for (String file : files)
if (file.contains("."))
res &= copyAsset(assetManager,
fromAssetPath + "/" + file,
toPath + "/" + file);
else
res &= copyAssetFolder(assetManager,
fromAssetPath + "/" + file,
toPath + "/" + file);
return res;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static boolean copyAsset(AssetManager assetManager,
String fromAssetPath, String toPath) {
InputStream in = null;
OutputStream out = null;
try {
in = assetManager.open(fromAssetPath);
new File(toPath).createNewFile();
out = new FileOutputStream(toPath);
copyFile(in, out);
in.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String readTextFromAssets(AssetManager assetManager, String fileName) {
try {
InputStreamReader inputReader = new InputStreamReader(assetManager.open(fileName));
BufferedReader bufReader = new BufferedReader(inputReader);
String line;
String Result = "";
while ((line = bufReader.readLine()) != null)
Result += line;
return Result;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String getAssetPath(Context context, String assetPath) {
InputStream in = null;
OutputStream out = null;
try {
context.deleteFile(assetPath);
in = context.getAssets().open(assetPath);
out = context.openFileOutput(assetPath, MODE_PRIVATE);
copyFile(in, out);
in.close();
String path = context.getFilesDir().toString();
return path + "/" + assetPath;
} catch (Exception e) {
e.printStackTrace();
return "";
} finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
}
}

View File

@@ -1,570 +0,0 @@
// Portions copyright 2002, Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.v2ray.ang.util;
// This code was converted from code at http://iharder.sourceforge.net/base64/
// Lots of extraneous features were removed.
/* The original code said:
* <p>
* I am placing this code in the Public Domain. Do with it as you will.
* This software comes with no guarantees or warranties but with
* plenty of well-wishing instead!
* Please visit
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
* periodically to check for updates or to contribute improvements.
* </p>
*
* @author Robert Harder
* @author rharder@usa.net
* @version 1.3
*/
/**
* Base64 converter class. This code is not a complete MIME encoder;
* it simply converts binary data to base64 data and back.
*
* <p>Note {@link CharBase64} is a GWT-compatible implementation of this
* class.
*/
public class Base64 {
/** Specify encoding (value is {@code true}). */
public final static boolean ENCODE = true;
/** Specify decoding (value is {@code false}). */
public final static boolean DECODE = false;
/** The equals sign (=) as a byte. */
private final static byte EQUALS_SIGN = (byte) '=';
/** The new line character (\n) as a byte. */
private final static byte NEW_LINE = (byte) '\n';
/**
* The 64 valid Base64 values.
*/
private final static byte[] ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '+', (byte) '/'};
/**
* The 64 valid web safe Base64 values.
*/
private final static byte[] WEBSAFE_ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '-', (byte) '_'};
/**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
**/
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
62, // Plus sign at decimal 43
-9, -9, -9, // Decimal 44 - 46
63, // Slash at decimal 47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
/** The web safe decodabet */
private final static byte[] WEBSAFE_DECODABET =
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
62, // Dash '-' sign at decimal 45
-9, -9, // Decimal 46-47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, // Decimal 91-94
63, // Underscore '_' at decimal 95
-9, // Decimal 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
// Indicates white space in encoding
private final static byte WHITE_SPACE_ENC = -5;
// Indicates equals sign in encoding
private final static byte EQUALS_SIGN_ENC = -1;
/** Defeats instantiation. */
private Base64() {
}
/* ******** E N C O D I N G M E T H O D S ******** */
/**
* Encodes up to three bytes of the array <var>source</var>
* and writes the resulting four Base64 bytes to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 3 for
* the <var>source</var> array or <var>destOffset</var> + 4 for
* the <var>destination</var> array.
* The actual number of significant bytes in your array is
* given by <var>numSigBytes</var>.
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param numSigBytes the number of significant bytes in your array
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param alphabet is the encoding alphabet
* @return the <var>destination</var> array
* @since 1.3
*/
private static byte[] encode3to4(byte[] source, int srcOffset,
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index alphabet
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
int inBuff =
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
switch (numSigBytes) {
case 3:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
return destination;
case 2:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
case 1:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = EQUALS_SIGN;
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
default:
return destination;
} // end switch
} // end encode3to4
/**
* Encodes a byte array into Base64 notation.
* Equivalent to calling
* {@code encodeBytes(source, 0, source.length)}
*
* @param source The data to convert
* @since 1.4
*/
public static String encode(byte[] source) {
return encode(source, 0, source.length, ALPHABET, true);
}
/**
* Encodes a byte array into web safe Base64 notation.
*
* @param source The data to convert
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
*/
public static String encodeWebSafe(byte[] source, boolean doPadding) {
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
}
/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet the encoding alphabet
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
* @since 1.4
*/
public static String encode(byte[] source, int off, int len, byte[] alphabet,
boolean doPadding) {
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
int outLen = outBuff.length;
// If doPadding is false, set length to truncate '='
// padding characters
while (doPadding == false && outLen > 0) {
if (outBuff[outLen - 1] != '=') {
break;
}
outLen -= 1;
}
return new String(outBuff, 0, outLen);
}
/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet is the encoding alphabet
* @param maxLineLength maximum length of one line.
* @return the BASE64-encoded byte array
*/
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
int maxLineLength) {
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
int len43 = lenDiv3 * 4;
byte[] outBuff = new byte[len43 // Main 4:3
+ (len43 / maxLineLength)]; // New lines
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for (; d < len2; d += 3, e += 4) {
// The following block of code is the same as
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
// but inlined for faster encoding (~20% improvement)
int inBuff =
((source[d + off] << 24) >>> 8)
| ((source[d + 1 + off] << 24) >>> 16)
| ((source[d + 2 + off] << 24) >>> 24);
outBuff[e] = alphabet[(inBuff >>> 18)];
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
lineLength += 4;
if (lineLength == maxLineLength) {
outBuff[e + 4] = NEW_LINE;
e++;
lineLength = 0;
} // end if: end of line
} // end for: each piece of array
if (d < len) {
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
lineLength += 4;
if (lineLength == maxLineLength) {
// Add a last newline
outBuff[e + 4] = NEW_LINE;
e++;
}
e += 4;
}
assert (e == outBuff.length);
return outBuff;
}
/* ******** D E C O D I N G M E T H O D S ******** */
/**
* Decodes four bytes from array <var>source</var>
* and writes the resulting bytes (up to three of them)
* to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 4 for
* the <var>source</var> array or <var>destOffset</var> + 3 for
* the <var>destination</var> array.
* This method returns the actual number of bytes that
* were converted from the Base64 encoding.
*
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param decodabet the decodabet for decoding Base64 content
* @return the number of decoded bytes converted
* @since 1.3
*/
private static int decode4to3(byte[] source, int srcOffset,
byte[] destination, int destOffset, byte[] decodabet) {
// Example: Dk==
if (source[srcOffset + 2] == EQUALS_SIGN) {
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
destination[destOffset] = (byte) (outBuff >>> 16);
return 1;
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
// Example: DkL=
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
destination[destOffset] = (byte) (outBuff >>> 16);
destination[destOffset + 1] = (byte) (outBuff >>> 8);
return 2;
} else {
// Example: DkLE
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
destination[destOffset] = (byte) (outBuff >> 16);
destination[destOffset + 1] = (byte) (outBuff >> 8);
destination[destOffset + 2] = (byte) (outBuff);
return 3;
}
} // end decodeToBytes
/**
* Decodes data from Base64 notation.
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
* @since 1.4
*/
public static byte[] decode(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decode(bytes, 0, bytes.length);
}
/**
* Decodes data from web safe Base64 notation.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decodeWebSafe(bytes, 0, bytes.length);
}
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source The Base64 encoded data
* @return decoded data
* @since 1.3
* @throws Base64DecoderException
*/
public static byte[] decode(byte[] source) throws Base64DecoderException {
return decode(source, 0, source.length);
}
/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded data.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(byte[] source)
throws Base64DecoderException {
return decodeWebSafe(source, 0, source.length);
}
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
* @since 1.3
* @throws Base64DecoderException
*/
public static byte[] decode(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, DECODABET);
}
/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded byte array.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
*/
public static byte[] decodeWebSafe(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, WEBSAFE_DECODABET);
}
/**
* Decodes Base64 content using the supplied decodabet and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @param decodabet the decodabet for decoding Base64 content
* @return decoded data
*/
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
throws Base64DecoderException {
int len34 = len * 3 / 4;
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
int outBuffPosn = 0;
byte[] b4 = new byte[4];
int b4Posn = 0;
int i = 0;
byte sbiCrop = 0;
byte sbiDecode = 0;
for (i = 0; i < len; i++) {
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
sbiDecode = decodabet[sbiCrop];
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
if (sbiDecode >= EQUALS_SIGN_ENC) {
// An equals sign (for padding) must not occur at position 0 or 1
// and must be the last byte[s] in the encoded value
if (sbiCrop == EQUALS_SIGN) {
int bytesLeft = len - i;
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
if (b4Posn == 0 || b4Posn == 1) {
throw new Base64DecoderException(
"invalid padding byte '=' at byte offset " + i);
} else if ((b4Posn == 3 && bytesLeft > 2)
|| (b4Posn == 4 && bytesLeft > 1)) {
throw new Base64DecoderException(
"padding byte '=' falsely signals end of encoded value "
+ "at offset " + i);
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
throw new Base64DecoderException(
"encoded value has invalid trailing byte");
}
break;
}
b4[b4Posn++] = sbiCrop;
if (b4Posn == 4) {
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
b4Posn = 0;
}
}
} else {
throw new Base64DecoderException("Bad Base64 input character at " + i
+ ": " + source[i + off] + "(decimal)");
}
}
// Because web safe encoding allows non padding base64 encodes, we
// need to pad the rest of the b4 buffer with equal signs when
// b4Posn != 0. There can be at most 2 equal signs at the end of
// four characters, so the b4 buffer must have two or three
// characters. This also catches the case where the input is
// padded with EQUALS_SIGN
if (b4Posn != 0) {
if (b4Posn == 1) {
throw new Base64DecoderException("single trailing character at offset "
+ (len - 1));
}
b4[b4Posn++] = EQUALS_SIGN;
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
}
byte[] out = new byte[outBuffPosn];
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
return out;
}
}

View File

@@ -1,32 +0,0 @@
// Copyright 2002, Google, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.v2ray.ang.util;
/**
* Exception thrown when encountering an invalid Base64 input character.
*
* @author nelson
*/
public class Base64DecoderException extends Exception {
public Base64DecoderException() {
super();
}
public Base64DecoderException(String s) {
super(s);
}
private static final long serialVersionUID = 1L;
}

View File

@@ -1,43 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
/**
* Exception thrown when something went wrong with in-app billing.
* An IabException has an associated IabResult (an error).
* To get the IAB result that caused this exception to be thrown,
* call {@link #getResult()}.
*/
public class IabException extends Exception {
IabResult mResult;
public IabException(IabResult r) {
this(r, null);
}
public IabException(int response, String message) {
this(new IabResult(response, message));
}
public IabException(IabResult r, Exception cause) {
super(r.getMessage(), cause);
mResult = r;
}
public IabException(int response, String message, Exception cause) {
this(new IabResult(response, message), cause);
}
/** Returns the IAB result (error) that this exception signals. */
public IabResult getResult() { return mResult; }
}

View File

@@ -1,979 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.android.vending.billing.IInAppBillingService;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.List;
/**
* Provides convenience methods for in-app billing. You can create one instance of this
* class for your application and use it to process in-app billing operations.
* It provides synchronous (blocking) and asynchronous (non-blocking) methods for
* many common in-app billing operations, as well as automatic signature
* verification.
* <p>
* After instantiating, you must perform setup in order to start using the object.
* To perform setup, call the {@link #startSetup} method and provide a listener;
* that listener will be notified when setup is complete, after which (and not before)
* you may call other methods.
* <p>
* After setup is complete, you will typically want to request an inventory of owned
* items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync}
* and related methods.
* <p>
* When you are done with this object, don't forget to call {@link #dispose}
* to ensure proper cleanup. This object holds a binding to the in-app billing
* service, which will leak unless you dispose of it correctly. If you created
* the object on an Activity's onCreate method, then the recommended
* place to dispose of it is the Activity's onDestroy method.
* <p>
* A note about threading: When using this object from a background thread, you may
* call the blocking versions of methods; when using from a UI thread, call
* only the asynchronous versions and handle the results via callbacks.
* Also, notice that you can only call one asynchronous operation at a time;
* attempting to start a second asynchronous operation while the first one
* has not yet completed will result in an exception being thrown.
*
* @author Bruno Oliveira (Google)
*/
public class IabHelper {
// Is debug logging enabled?
boolean mDebugLog = false;
String mDebugTag = "IabHelper";
// Is setup done?
boolean mSetupDone = false;
// Has this object been disposed of? (If so, we should ignore callbacks, etc)
boolean mDisposed = false;
// Are subscriptions supported?
boolean mSubscriptionsSupported = false;
// Is an asynchronous operation in progress?
// (only one at a time can be in progress)
boolean mAsyncInProgress = false;
// (for logging/debugging)
// if mAsyncInProgress == true, what asynchronous operation is in progress?
String mAsyncOperation = "";
// Context we were passed during initialization
Context mContext;
// Connection to the service
IInAppBillingService mService;
ServiceConnection mServiceConn;
// The request code used to launch purchase flow
int mRequestCode;
// The item type of the current purchase flow
String mPurchasingItemType;
// Public key for verifying signature, in base64 encoding
String mSignatureBase64 = null;
// Billing response codes
public static final int BILLING_RESPONSE_RESULT_OK = 0;
public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1;
public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3;
public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4;
public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5;
public static final int BILLING_RESPONSE_RESULT_ERROR = 6;
public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7;
public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8;
// IAB Helper error codes
public static final int IABHELPER_ERROR_BASE = -1000;
public static final int IABHELPER_REMOTE_EXCEPTION = -1001;
public static final int IABHELPER_BAD_RESPONSE = -1002;
public static final int IABHELPER_VERIFICATION_FAILED = -1003;
public static final int IABHELPER_SEND_INTENT_FAILED = -1004;
public static final int IABHELPER_USER_CANCELLED = -1005;
public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006;
public static final int IABHELPER_MISSING_TOKEN = -1007;
public static final int IABHELPER_UNKNOWN_ERROR = -1008;
public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009;
public static final int IABHELPER_INVALID_CONSUMPTION = -1010;
// Keys for the responses from InAppBillingService
public static final String RESPONSE_CODE = "RESPONSE_CODE";
public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST";
public static final String RESPONSE_BUY_INTENT = "BUY_INTENT";
public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA";
public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE";
public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST";
public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST";
public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST";
public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN";
// Item types
public static final String ITEM_TYPE_INAPP = "inapp";
public static final String ITEM_TYPE_SUBS = "subs";
// some fields on the getSkuDetails response bundle
public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST";
public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST";
/**
* Creates an instance. After creation, it will not yet be ready to use. You must perform
* setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not
* block and is safe to call from a UI thread.
*
* @param ctx Your application or Activity context. Needed to bind to the in-app billing service.
* @param base64PublicKey Your application's public key, encoded in base64.
* This is used for verification of purchase signatures. You can find your app's base64-encoded
* public key in your application's page on Google Play Developer Console. Note that this
* is NOT your "developer public key".
*/
public IabHelper(Context ctx, String base64PublicKey) {
mContext = ctx.getApplicationContext();
mSignatureBase64 = base64PublicKey;
logDebug("IAB helper created.");
}
/**
* Enables or disable debug logging through LogCat.
*/
public void enableDebugLogging(boolean enable, String tag) {
checkNotDisposed();
mDebugLog = enable;
mDebugTag = tag;
}
public void enableDebugLogging(boolean enable) {
checkNotDisposed();
mDebugLog = enable;
}
/**
* Callback for setup process. This listener's {@link #onIabSetupFinished} method is called
* when the setup process is complete.
*/
public interface OnIabSetupFinishedListener {
/**
* Called to notify that setup is complete.
*
* @param result The result of the setup process.
*/
void onIabSetupFinished(IabResult result);
}
/**
* Starts the setup process. This will start up the setup process asynchronously.
* You will be notified through the listener when the setup process is complete.
* This method is safe to call from a UI thread.
*
* @param listener The listener to notify when the setup process is complete.
*/
public void startSetup(final OnIabSetupFinishedListener listener) {
// If already set up, can't do it again.
checkNotDisposed();
if (mSetupDone) throw new IllegalStateException("IAB helper is already set up.");
// Connection to IAB service
logDebug("Starting in-app billing setup.");
mServiceConn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
logDebug("Billing service disconnected.");
mService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDisposed) return;
logDebug("Billing service connected.");
mService = IInAppBillingService.Stub.asInterface(service);
String packageName = mContext.getPackageName();
try {
logDebug("Checking for in-app billing 3 support.");
// check for in-app billing v3 support
int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP);
if (response != BILLING_RESPONSE_RESULT_OK) {
if (listener != null) listener.onIabSetupFinished(new IabResult(response,
"Error checking for billing v3 support."));
// if in-app purchases aren't supported, neither are subscriptions.
mSubscriptionsSupported = false;
return;
}
logDebug("In-app billing version 3 supported for " + packageName);
// check for v3 subscriptions support
response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Subscriptions AVAILABLE.");
mSubscriptionsSupported = true;
} else {
logDebug("Subscriptions NOT AVAILABLE. Response: " + response);
}
mSetupDone = true;
} catch (RemoteException e) {
if (listener != null) {
listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
"RemoteException while setting up in-app billing."));
}
e.printStackTrace();
return;
}
if (listener != null) {
listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
}
}
};
// Intent serviceIntent = new Intent("ir.cafebazaar.pardakht.InAppBillingService.BIND");
// serviceIntent.setPackage("com.farsitel.bazaar");
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
// service available to handle that Intent
mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
} else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
}
}
}
/**
* Dispose of object, releasing resources. It's very important to call this
* method when you are done with this object. It will release any resources
* used by it such as service connections. Naturally, once the object is
* disposed of, it can't be used again.
*/
public void dispose() {
logDebug("Disposing.");
mSetupDone = false;
if (mServiceConn != null) {
logDebug("Unbinding from service.");
if (mContext != null) mContext.unbindService(mServiceConn);
}
mDisposed = true;
mContext = null;
mServiceConn = null;
mService = null;
mPurchaseListener = null;
}
private void checkNotDisposed() {
if (mDisposed)
throw new IllegalStateException("IabHelper was disposed of, so it cannot be used.");
}
/**
* Returns whether subscriptions are supported.
*/
public boolean subscriptionsSupported() {
checkNotDisposed();
return mSubscriptionsSupported;
}
/**
* Callback that notifies when a purchase is finished.
*/
public interface OnIabPurchaseFinishedListener {
/**
* Called to notify that an in-app purchase finished. If the purchase was successful,
* then the sku parameter specifies which item was purchased. If the purchase failed,
* the sku and extraData parameters may or may not be null, depending on how far the purchase
* process went.
*
* @param result The result of the purchase.
* @param info The purchase information (null if purchase failed)
*/
void onIabPurchaseFinished(IabResult result, Purchase info);
}
// The listener registered on launchPurchaseFlow, which we have to call back when
// the purchase finishes
OnIabPurchaseFinishedListener mPurchaseListener;
public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) {
launchPurchaseFlow(act, sku, requestCode, listener, "");
}
public void launchPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData);
}
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener) {
launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, "");
}
public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData);
}
/**
* Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase,
* which will involve bringing up the Google Play screen. The calling activity will be paused while
* the user interacts with Google Play, and the result will be delivered via the activity's
* {@link android.app.Activity#onActivityResult} method, at which point you must call
* this object's {@link #handleActivityResult} method to continue the purchase flow. This method
* MUST be called from the UI thread of the Activity.
*
* @param act The calling activity.
* @param sku The sku of the item to purchase.
* @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS)
* @param requestCode A request code (to differentiate from other responses --
* as in {@link android.app.Activity#startActivityForResult}).
* @param listener The listener to notify when the purchase process finishes
* @param extraData Extra data (developer payload), which will be returned with the purchase data
* when the purchase completes. This extra data will be permanently bound to that purchase
* and will always be returned when the purchase is queried.
*/
public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode,
OnIabPurchaseFinishedListener listener, String extraData) {
checkNotDisposed();
checkSetupDone("launchPurchaseFlow");
flagStartAsync("launchPurchaseFlow");
IabResult result;
if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) {
IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE,
"Subscriptions are not available.");
flagEndAsync();
if (listener != null) listener.onIabPurchaseFinished(r, null);
return;
}
try {
logDebug("Constructing buy intent for " + sku + ", item type: " + itemType);
Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData);
int response = getResponseCodeFromBundle(buyIntentBundle);
if (response != BILLING_RESPONSE_RESULT_OK) {
logError("Unable to buy item, Error response: " + getResponseDesc(response));
flagEndAsync();
result = new IabResult(response, "Unable to buy item");
if (listener != null) listener.onIabPurchaseFinished(result, null);
return;
}
PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT);
logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode);
mRequestCode = requestCode;
mPurchaseListener = listener;
mPurchasingItemType = itemType;
act.startIntentSenderForResult(pendingIntent.getIntentSender(),
requestCode, new Intent(),
Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
} catch (SendIntentException e) {
logError("SendIntentException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent.");
if (listener != null) listener.onIabPurchaseFinished(result, null);
} catch (RemoteException e) {
logError("RemoteException while launching purchase flow for sku " + sku);
e.printStackTrace();
flagEndAsync();
result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow");
if (listener != null) listener.onIabPurchaseFinished(result, null);
}
}
/**
* Handles an activity result that's part of the purchase flow in in-app billing. If you
* are calling {@link #launchPurchaseFlow}, then you must call this method from your
* Activity's {@link android.app.Activity@onActivityResult} method. This method
* MUST be called from the UI thread of the Activity.
*
* @param requestCode The requestCode as you received it.
* @param resultCode The resultCode as you received it.
* @param data The data (Intent) as you received it.
* @return Returns true if the result was related to a purchase flow and was handled;
* false if the result was not related to a purchase, in which case you should
* handle it normally.
*/
public boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
IabResult result;
if (requestCode != mRequestCode) return false;
checkNotDisposed();
checkSetupDone("handleActivityResult");
// end of async purchase operation that started on launchPurchaseFlow
flagEndAsync();
if (data == null) {
logError("Null data in IAB activity result.");
result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
int responseCode = getResponseCodeFromIntent(data);
String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA);
String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE);
if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successful resultcode from purchase activity.");
logDebug("Purchase data: " + purchaseData);
logDebug("Data signature: " + dataSignature);
logDebug("Extras: " + data.getExtras());
logDebug("Expected item type: " + mPurchasingItemType);
if (purchaseData == null || dataSignature == null) {
logError("BUG: either purchaseData or dataSignature is null.");
logDebug("Extras: " + data.getExtras().toString());
result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature");
if (mPurchaseListener != null)
mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
Purchase purchase = null;
try {
purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature);
String sku = purchase.getSku();
// Verify signature
if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) {
logError("Purchase signature verification FAILED for sku " + sku);
result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku);
if (mPurchaseListener != null)
mPurchaseListener.onIabPurchaseFinished(result, purchase);
return true;
}
logDebug("Purchase signature successfully verified.");
} catch (JSONException e) {
logError("Failed to parse purchase data.");
e.printStackTrace();
result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data.");
if (mPurchaseListener != null)
mPurchaseListener.onIabPurchaseFinished(result, null);
return true;
}
if (mPurchaseListener != null) {
mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase);
}
} else if (resultCode == Activity.RESULT_OK) {
// result code was OK, but in-app billing response was not OK.
logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode));
if (mPurchaseListener != null) {
result = new IabResult(responseCode, "Problem purchashing item.");
mPurchaseListener.onIabPurchaseFinished(result, null);
}
} else if (resultCode == Activity.RESULT_CANCELED) {
logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
} else {
logError("Purchase failed. Result code: " + Integer.toString(resultCode)
+ ". Response: " + getResponseDesc(responseCode));
result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response.");
if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null);
}
return true;
}
public Inventory queryInventory(boolean querySkuDetails, List<String> moreSkus) throws IabException {
return queryInventory(querySkuDetails, moreSkus, null);
}
/**
* Queries the inventory. This will query all owned items from the server, as well as
* information on additional skus, if specified. This method may block or take long to execute.
* Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}.
*
* @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well
* as purchase information.
* @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership.
* Ignored if null or if querySkuDetails is false.
* @throws IabException if a problem occurs while refreshing the inventory.
*/
public Inventory queryInventory(boolean querySkuDetails, List<String> moreItemSkus,
List<String> moreSubsSkus) throws IabException {
checkNotDisposed();
checkSetupDone("queryInventory");
try {
Inventory inv = new Inventory();
int r = queryPurchases(inv, ITEM_TYPE_INAPP);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying owned items).");
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of items).");
}
}
// if subscriptions are supported, then also query for subscriptions
if (mSubscriptionsSupported) {
r = queryPurchases(inv, ITEM_TYPE_SUBS);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying owned subscriptions).");
}
if (querySkuDetails) {
r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus);
if (r != BILLING_RESPONSE_RESULT_OK) {
throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
}
}
}
return inv;
} catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e);
} catch (JSONException e) {
throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e);
}
}
/**
* Listener that notifies when an inventory query operation completes.
*/
public interface QueryInventoryFinishedListener {
/**
* Called to notify that an inventory query operation completed.
*
* @param result The result of the operation.
* @param inv The inventory.
*/
void onQueryInventoryFinished(IabResult result, Inventory inv);
}
/**
* Asynchronous wrapper for inventory query. This will perform an inventory
* query as described in {@link #queryInventory}, but will do so asynchronously
* and call back the specified listener upon completion. This method is safe to
* call from a UI thread.
*
* @param querySkuDetails as in {@link #queryInventory}
* @param moreSkus as in {@link #queryInventory}
* @param listener The listener to notify when the refresh operation completes.
*/
public void queryInventoryAsync(final boolean querySkuDetails,
final List<String> moreSkus,
final QueryInventoryFinishedListener listener) {
final Handler handler = new Handler();
checkNotDisposed();
checkSetupDone("queryInventory");
flagStartAsync("refresh inventory");
(new Thread(new Runnable() {
public void run() {
IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful.");
Inventory inv = null;
try {
inv = queryInventory(querySkuDetails, moreSkus);
} catch (IabException ex) {
result = ex.getResult();
}
flagEndAsync();
final IabResult result_f = result;
final Inventory inv_f = inv;
if (!mDisposed && listener != null) {
handler.post(new Runnable() {
public void run() {
listener.onQueryInventoryFinished(result_f, inv_f);
}
});
}
}
})).start();
}
public void queryInventoryAsync(QueryInventoryFinishedListener listener) {
queryInventoryAsync(true, null, listener);
}
public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) {
queryInventoryAsync(querySkuDetails, null, listener);
}
/**
* Consumes a given in-app product. Consuming can only be done on an item
* that's owned, and as a result of consumption, the user will no longer own it.
* This method may block or take long to return. Do not call from the UI thread.
* For that, see {@link #consumeAsync}.
*
* @param itemInfo The PurchaseInfo that represents the item to consume.
* @throws IabException if there is a problem during consumption.
*/
void consume(Purchase itemInfo) throws IabException {
checkNotDisposed();
checkSetupDone("consume");
if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) {
throw new IabException(IABHELPER_INVALID_CONSUMPTION,
"Items of type '" + itemInfo.mItemType + "' can't be consumed.");
}
try {
String token = itemInfo.getToken();
String sku = itemInfo.getSku();
if (token == null || token.equals("")) {
logError("Can't consume " + sku + ". No token.");
throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: "
+ sku + " " + itemInfo);
}
logDebug("Consuming sku: " + sku + ", token: " + token);
int response = mService.consumePurchase(3, mContext.getPackageName(), token);
if (response == BILLING_RESPONSE_RESULT_OK) {
logDebug("Successfully consumed sku: " + sku);
} else {
logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response));
throw new IabException(response, "Error consuming sku " + sku);
}
} catch (RemoteException e) {
throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e);
}
}
/**
* Callback that notifies when a consumption operation finishes.
*/
public interface OnConsumeFinishedListener {
/**
* Called to notify that a consumption has finished.
*
* @param purchase The purchase that was (or was to be) consumed.
* @param result The result of the consumption operation.
*/
void onConsumeFinished(Purchase purchase, IabResult result);
}
/**
* Callback that notifies when a multi-item consumption operation finishes.
*/
public interface OnConsumeMultiFinishedListener {
/**
* Called to notify that a consumption of multiple items has finished.
*
* @param purchases The purchases that were (or were to be) consumed.
* @param results The results of each consumption operation, corresponding to each
* sku.
*/
void onConsumeMultiFinished(List<Purchase> purchases, List<IabResult> results);
}
/**
* Asynchronous wrapper to item consumption. Works like {@link #consume}, but
* performs the consumption in the background and notifies completion through
* the provided listener. This method is safe to call from a UI thread.
*
* @param purchase The purchase to be consumed.
* @param listener The listener to notify when the consumption operation finishes.
*/
public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) {
checkNotDisposed();
checkSetupDone("consume");
List<Purchase> purchases = new ArrayList<Purchase>();
purchases.add(purchase);
consumeAsyncInternal(purchases, listener, null);
}
/**
* Same as {@link consumeAsync}, but for multiple items at once.
*
* @param purchases The list of PurchaseInfo objects representing the purchases to consume.
* @param listener The listener to notify when the consumption operation finishes.
*/
public void consumeAsync(List<Purchase> purchases, OnConsumeMultiFinishedListener listener) {
checkNotDisposed();
checkSetupDone("consume");
consumeAsyncInternal(purchases, null, listener);
}
/**
* Returns a human-readable description for the given response code.
*
* @param code The response code
* @return A human-readable string explaining the result code.
* It also includes the result code numerically.
*/
public static String getResponseDesc(int code) {
String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" +
"3:Billing Unavailable/4:Item unavailable/" +
"5:Developer Error/6:Error/7:Item Already Owned/" +
"8:Item not owned").split("/");
String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" +
"-1002:Bad response received/" +
"-1003:Purchase signature verification failed/" +
"-1004:Send intent failed/" +
"-1005:User cancelled/" +
"-1006:Unknown purchase response/" +
"-1007:Missing token/" +
"-1008:Unknown error/" +
"-1009:Subscriptions not available/" +
"-1010:Invalid consumption attempt").split("/");
if (code <= IABHELPER_ERROR_BASE) {
int index = IABHELPER_ERROR_BASE - code;
if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index];
else return String.valueOf(code) + ":Unknown IAB Helper Error";
} else if (code < 0 || code >= iab_msgs.length)
return String.valueOf(code) + ":Unknown";
else
return iab_msgs[code];
}
// Checks that setup was done; if not, throws an exception.
void checkSetupDone(String operation) {
if (!mSetupDone) {
logError("Illegal state for operation (" + operation + "): IAB helper is not set up.");
throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation);
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int getResponseCodeFromBundle(Bundle b) {
Object o = b.get(RESPONSE_CODE);
if (o == null) {
logDebug("Bundle with null response code, assuming OK (known issue)");
return BILLING_RESPONSE_RESULT_OK;
} else if (o instanceof Integer) return ((Integer) o).intValue();
else if (o instanceof Long) return (int) ((Long) o).longValue();
else {
logError("Unexpected type for bundle response code.");
logError(o.getClass().getName());
throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName());
}
}
// Workaround to bug where sometimes response codes come as Long instead of Integer
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras().get(RESPONSE_CODE);
if (o == null) {
logError("Intent with no response code, assuming OK (known issue)");
return BILLING_RESPONSE_RESULT_OK;
} else if (o instanceof Integer) return ((Integer) o).intValue();
else if (o instanceof Long) return (int) ((Long) o).longValue();
else {
logError("Unexpected type for intent response code.");
logError(o.getClass().getName());
throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName());
}
}
void flagStartAsync(String operation) {
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
mAsyncOperation = operation;
mAsyncInProgress = true;
logDebug("Starting async operation: " + operation);
}
void flagEndAsync() {
logDebug("Ending async operation: " + mAsyncOperation);
mAsyncOperation = "";
mAsyncInProgress = false;
}
int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
// Query purchases
logDebug("Querying owned items, item type: " + itemType);
logDebug("Package name: " + mContext.getPackageName());
boolean verificationFailed = false;
String continueToken = null;
do {
logDebug("Calling getPurchases with continuation token: " + continueToken);
Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
itemType, continueToken);
int response = getResponseCodeFromBundle(ownedItems);
logDebug("Owned items response: " + String.valueOf(response));
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getPurchases() failed: " + getResponseDesc(response));
return response;
}
if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST)
|| !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) {
logError("Bundle returned from getPurchases() doesn't contain required fields.");
return IABHELPER_BAD_RESPONSE;
}
ArrayList<String> ownedSkus = ownedItems.getStringArrayList(
RESPONSE_INAPP_ITEM_LIST);
ArrayList<String> purchaseDataList = ownedItems.getStringArrayList(
RESPONSE_INAPP_PURCHASE_DATA_LIST);
ArrayList<String> signatureList = ownedItems.getStringArrayList(
RESPONSE_INAPP_SIGNATURE_LIST);
for (int i = 0; i < purchaseDataList.size(); ++i) {
String purchaseData = purchaseDataList.get(i);
String signature = signatureList.get(i);
String sku = ownedSkus.get(i);
if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) {
logDebug("Sku is owned: " + sku);
Purchase purchase = new Purchase(itemType, purchaseData, signature);
if (TextUtils.isEmpty(purchase.getToken())) {
logWarn("BUG: empty/null token!");
logDebug("Purchase data: " + purchaseData);
}
// Record ownership and token
inv.addPurchase(purchase);
} else {
logWarn("Purchase signature verification **FAILED**. Not adding item.");
logDebug(" Purchase data: " + purchaseData);
logDebug(" Signature: " + signature);
verificationFailed = true;
}
}
continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN);
logDebug("Continuation token: " + continueToken);
} while (!TextUtils.isEmpty(continueToken));
return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK;
}
int querySkuDetails(String itemType, Inventory inv, List<String> moreSkus)
throws RemoteException, JSONException {
logDebug("Querying SKU details.");
ArrayList<String> skuList = new ArrayList<String>();
skuList.addAll(inv.getAllOwnedSkus(itemType));
if (moreSkus != null) {
for (String sku : moreSkus) {
if (!skuList.contains(sku)) {
skuList.add(sku);
}
}
}
if (skuList.size() == 0) {
logDebug("queryPrices: nothing to do because there are no SKUs.");
return BILLING_RESPONSE_RESULT_OK;
}
Bundle querySkus = new Bundle();
querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(),
itemType, querySkus);
if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) {
int response = getResponseCodeFromBundle(skuDetails);
if (response != BILLING_RESPONSE_RESULT_OK) {
logDebug("getSkuDetails() failed: " + getResponseDesc(response));
return response;
} else {
logError("getSkuDetails() returned a bundle with neither an error nor a detail list.");
return IABHELPER_BAD_RESPONSE;
}
}
ArrayList<String> responseList = skuDetails.getStringArrayList(
RESPONSE_GET_SKU_DETAILS_LIST);
for (String thisResponse : responseList) {
SkuDetails d = new SkuDetails(itemType, thisResponse);
logDebug("Got sku details: " + d);
inv.addSkuDetails(d);
}
return BILLING_RESPONSE_RESULT_OK;
}
void consumeAsyncInternal(final List<Purchase> purchases,
final OnConsumeFinishedListener singleListener,
final OnConsumeMultiFinishedListener multiListener) {
final Handler handler = new Handler();
flagStartAsync("consume");
(new Thread(new Runnable() {
public void run() {
final List<IabResult> results = new ArrayList<IabResult>();
for (Purchase purchase : purchases) {
try {
consume(purchase);
results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku()));
} catch (IabException ex) {
results.add(ex.getResult());
}
}
flagEndAsync();
if (!mDisposed && singleListener != null) {
handler.post(new Runnable() {
public void run() {
singleListener.onConsumeFinished(purchases.get(0), results.get(0));
}
});
}
if (!mDisposed && multiListener != null) {
handler.post(new Runnable() {
public void run() {
multiListener.onConsumeMultiFinished(purchases, results);
}
});
}
}
})).start();
}
void logDebug(String msg) {
if (mDebugLog) Log.d(mDebugTag, msg);
}
void logError(String msg) {
Log.e(mDebugTag, "In-app billing error: " + msg);
}
void logWarn(String msg) {
Log.w(mDebugTag, "In-app billing warning: " + msg);
}
}

View File

@@ -1,45 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
/**
* Represents the result of an in-app billing operation.
* A result is composed of a response code (an integer) and possibly a
* message (String). You can get those by calling
* {@link #getResponse} and {@link #getMessage()}, respectively. You
* can also inquire whether a result is a success or a failure by
* calling {@link #isSuccess()} and {@link #isFailure()}.
*/
public class IabResult {
int mResponse;
String mMessage;
public IabResult(int response, String message) {
mResponse = response;
if (message == null || message.trim().length() == 0) {
mMessage = IabHelper.getResponseDesc(response);
}
else {
mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")";
}
}
public int getResponse() { return mResponse; }
public String getMessage() { return mMessage; }
public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; }
public boolean isFailure() { return !isSuccess(); }
public String toString() { return "IabResult: " + getMessage(); }
}

View File

@@ -1,91 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a block of information about in-app items.
* An Inventory is returned by such methods as {@link IabHelper#queryInventory}.
*/
public class Inventory {
Map<String,SkuDetails> mSkuMap = new HashMap<String,SkuDetails>();
Map<String,Purchase> mPurchaseMap = new HashMap<String,Purchase>();
Inventory() { }
/** Returns the listing details for an in-app product. */
public SkuDetails getSkuDetails(String sku) {
return mSkuMap.get(sku);
}
/** Returns purchase information for a given product, or null if there is no purchase. */
public Purchase getPurchase(String sku) {
return mPurchaseMap.get(sku);
}
/** Returns whether or not there exists a purchase of the given product. */
public boolean hasPurchase(String sku) {
return mPurchaseMap.containsKey(sku);
}
/** Return whether or not details about the given product are available. */
public boolean hasDetails(String sku) {
return mSkuMap.containsKey(sku);
}
/**
* Erase a purchase (locally) from the inventory, given its product ID. This just
* modifies the Inventory object locally and has no effect on the server! This is
* useful when you have an existing Inventory object which you know to be up to date,
* and you have just consumed an item successfully, which means that erasing its
* purchase data from the Inventory you already have is quicker than querying for
* a new Inventory.
*/
public void erasePurchase(String sku) {
if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku);
}
/** Returns a list of all owned product IDs. */
List<String> getAllOwnedSkus() {
return new ArrayList<String>(mPurchaseMap.keySet());
}
/** Returns a list of all owned product IDs of a given type */
List<String> getAllOwnedSkus(String itemType) {
List<String> result = new ArrayList<String>();
for (Purchase p : mPurchaseMap.values()) {
if (p.getItemType().equals(itemType)) result.add(p.getSku());
}
return result;
}
/** Returns a list of all purchases. */
List<Purchase> getAllPurchases() {
return new ArrayList<Purchase>(mPurchaseMap.values());
}
void addSkuDetails(SkuDetails d) {
mSkuMap.put(d.getSku(), d);
}
void addPurchase(Purchase p) {
mPurchaseMap.put(p.getSku(), p);
}
}

View File

@@ -1,540 +0,0 @@
package com.v2ray.ang.util;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Reference to http://blog.csdn.net/way_ping_li/article/details/8487866
* and improved some features...
*/
public class LogRecorder {
public static final int LOG_LEVEL_NO_SET = 0;
public static final int LOG_BUFFER_MAIN = 1;
public static final int LOG_BUFFER_SYSTEM = 1 << 1;
public static final int LOG_BUFFER_RADIO = 1 << 2;
public static final int LOG_BUFFER_EVENTS = 1 << 3;
public static final int LOG_BUFFER_KERNEL = 1 << 4; // not be supported by now
public static final int LOG_BUFFER_DEFAULT = LOG_BUFFER_MAIN | LOG_BUFFER_SYSTEM;
public static final int INVALID_PID = -1;
public String mFileSuffix;
public String mFolderPath;
public int mFileSizeLimitation;
public int mLevel;
public List<String> mFilterTags = new ArrayList<>();
public int mPID = INVALID_PID;
public boolean mUseLogcatFileOut = false;
private LogDumper mLogDumper = null;
public static final int EVENT_RESTART_LOG = 1001;
private RestartHandler mHandler;
private static class RestartHandler extends Handler {
final LogRecorder logRecorder;
public RestartHandler(LogRecorder logRecorder) {
this.logRecorder = logRecorder;
}
@Override
public void handleMessage(Message msg) {
if (msg.what == EVENT_RESTART_LOG) {
logRecorder.stop();
logRecorder.start();
}
}
}
public LogRecorder() {
mHandler = new RestartHandler(this);
}
public void start() {
// make sure the out folder exist
// TODO support multi-phase path
File file = new File(mFolderPath);
if (!file.exists()) {
file.mkdirs();
}
String cmdStr = collectLogcatCommand();
if (mLogDumper != null) {
mLogDumper.stopDumping();
mLogDumper = null;
}
mLogDumper = new LogDumper(mFolderPath, mFileSuffix, mFileSizeLimitation, cmdStr, mHandler);
mLogDumper.start();
}
public void stop() {
// TODO maybe should clean the log buffer first?
if (mLogDumper != null) {
mLogDumper.stopDumping();
mLogDumper = null;
}
}
private String collectLogcatCommand() {
StringBuilder stringBuilder = new StringBuilder();
final String SPACE = " ";
stringBuilder.append("logcat");
// TODO select ring buffer, -b
// TODO set out format
stringBuilder.append(SPACE);
stringBuilder.append("-v time");
// append tag filters
String levelStr = getLevelStr();
if (!mFilterTags.isEmpty()) {
stringBuilder.append(SPACE);
stringBuilder.append("-s");
for (int i = 0; i < mFilterTags.size(); i++) {
String tag = mFilterTags.get(i) + ":" + levelStr;
stringBuilder.append(SPACE);
stringBuilder.append(tag);
}
} else {
if (!TextUtils.isEmpty(levelStr)) {
stringBuilder.append(SPACE);
stringBuilder.append("*:" + levelStr);
}
}
// logcat -f , but the rotated count default is 4?
// can`t be sure to use that feature
if (mPID != INVALID_PID) {
mUseLogcatFileOut = false;
String pidStr = adjustPIDStr();
if (!TextUtils.isEmpty(pidStr)) {
stringBuilder.append(SPACE);
stringBuilder.append("|");
stringBuilder.append(SPACE);
stringBuilder.append("grep (" + pidStr + ")");
}
}
return stringBuilder.toString();
}
private String getLevelStr() {
switch (mLevel) {
case 2:
return "V";
case 3:
return "D";
case 4:
return "I";
case 5:
return "W";
case 6:
return "E";
case 7:
return "F";
}
return "V";
}
/**
* Android`s user app pid is bigger than 1000.
*
* @return
*/
private String adjustPIDStr() {
if (mPID == INVALID_PID) {
return null;
}
String pidStr = String.valueOf(mPID);
int length = pidStr.length();
if (length < 4) {
pidStr = " 0" + pidStr;
}
if (length == 4) {
pidStr = " " + pidStr;
}
return pidStr;
}
private class LogDumper extends Thread {
final String logPath;
final String logFileSuffix;
final int logFileLimitation;
final String logCmd;
final RestartHandler restartHandler;
private Process logcatProc;
private BufferedReader mReader = null;
private FileOutputStream out = null;
private boolean mRunning = true;
final private Object mRunningLock = new Object();
private long currentFileSize;
public LogDumper(String folderPath, String suffix,
int fileSizeLimitation, String command,
RestartHandler handler) {
logPath = folderPath;
logFileSuffix = suffix;
logFileLimitation = fileSizeLimitation;
logCmd = command;
restartHandler = handler;
String date = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
.format(new Date(System.currentTimeMillis()));
String fileName = (TextUtils.isEmpty(logFileSuffix)) ? date : (logFileSuffix + "-"+ date);
try {
out = new FileOutputStream(new File(logPath, fileName + ".log"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void stopDumping() {
synchronized (mRunningLock) {
mRunning = false;
}
}
@Override
public void run() {
try {
logcatProc = Runtime.getRuntime().exec(logCmd);
mReader = new BufferedReader(new InputStreamReader(
logcatProc.getInputStream()), 1024);
String line = null;
while (mRunning && (line = mReader.readLine()) != null) {
if (!mRunning) {
break;
}
if (line.length() == 0) {
continue;
}
if (out != null && !line.isEmpty()) {
byte[] data = (line + "\n").getBytes();
out.write(data);
if (logFileLimitation != 0) {
currentFileSize += data.length;
if (currentFileSize > logFileLimitation*1024) {
restartHandler.sendEmptyMessage(EVENT_RESTART_LOG);
break;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (logcatProc != null) {
logcatProc.destroy();
logcatProc = null;
}
if (mReader != null) {
try {
mReader.close();
mReader = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
out = null;
}
}
}
}
public static class Builder {
/**
* context object
*/
private Context mContext;
/**
* the folder name that we save log files to,
* just folder name, not the whole path,
* if set this, will save log files to /sdcard/$mLogFolderName folder,
* use /sdcard/$ApplicationName as default.
*/
private String mLogFolderName;
/**
* the whole folder path that we save log files to,
* this setting`s priority is bigger than folder name.
*/
private String mLogFolderPath;
/**
* the log file suffix,
* if this is sot, it will be appended to log file name automatically
*/
private String mLogFileNameSuffix = "";
/**
* single log file size limitation,
* in k-bytes, ex. set to 16, is 16KB limitation.
*/
private int mLogFileSizeLimitation = 0;
/**
* log level, see android.util.Log, 2 - 7,
* if not be set, will use verbose as default
*/
private int mLogLevel = LogRecorder.LOG_LEVEL_NO_SET;
/**
* can set several filter tags
* logcat -s ActivityManager:V SystemUI:V
*/
private List<String> mLogFilterTags = new ArrayList<>();
/**
* filter through pid, by setting this with your APP PID,
* the log recorder will just record the APP`s own log,
* use one call: android.os.Process.myPid().
*/
private int mPID = LogRecorder.INVALID_PID;
/**
* which log buffer to catch...
* <p/>
* Request alternate ring buffer, 'main', 'system', 'radio'
* or 'events'. Multiple -b parameters are allowed and the
* results are interleaved.
* <p/>
* The default is -b main -b system.
*/
private int mLogBuffersSelected = LogRecorder.LOG_BUFFER_DEFAULT;
/**
* log output format, don`t support config yet, use $time format as default.
* <p/>
* Log messages contain a number of metadata fields, in addition to the tag and priority.
* You can modify the output format for messages so that they display a specific metadata
* field. To do so, you use the -v option and specify one of the supported output formats
* listed below.
* <p/>
* brief — Display priority/tag and PID of the process issuing the message.
* process — Display PID only.
* tag — Display the priority/tag only.
* thread - Display the priority, tag, and the PID(process ID) and TID(thread ID)
* of the thread issuing the message.
* raw — Display the raw log message, with no other metadata fields.
* time — Display the date, invocation time, priority/tag, and PID of
* the process issuing the message.
* threadtime — Display the date, invocation time, priority, tag, and the PID(process ID)
* and TID(thread ID) of the thread issuing the message.
* long — Display all metadata fields and separate messages with blank lines.
*/
private int mLogOutFormat;
/**
* set log out folder name
*
* @param logFolderName folder name
* @return The same Builder.
*/
public Builder setLogFolderName(String logFolderName) {
this.mLogFolderName = logFolderName;
return this;
}
/**
* set log out folder path
*
* @param logFolderPath out folder absolute path
* @return the same Builder
*/
public Builder setLogFolderPath(String logFolderPath) {
this.mLogFolderPath = logFolderPath;
return this;
}
/**
* set log file name suffix
*
* @param logFileNameSuffix auto appened suffix
* @return the same Builder
*/
public Builder setLogFileNameSuffix(String logFileNameSuffix) {
this.mLogFileNameSuffix = logFileNameSuffix;
return this;
}
/**
* set the file size limitation
*
* @param fileSizeLimitation file size limitation in KB
* @return the same Builder
*/
public Builder setLogFileSizeLimitation(int fileSizeLimitation) {
this.mLogFileSizeLimitation = fileSizeLimitation;
return this;
}
/**
* set the log level
*
* @param logLevel log level, 2-7
* @return the same Builder
*/
public Builder setLogLevel(int logLevel) {
this.mLogLevel = logLevel;
return this;
}
/**
* add log filterspec tag name, can add multiple ones,
* they use the same log level set by setLogLevel()
*
* @param tag tag name
* @return the same Builder
*/
public Builder addLogFilterTag(String tag) {
mLogFilterTags.add(tag);
return this;
}
/**
* which process`s log
*
* @param mPID process id
* @return the same Builder
*/
public Builder setPID(int mPID) {
this.mPID = mPID;
return this;
}
/**
* -b radio, -b main, -b system, -b events
* -b main -b system as default
*
* @param logBuffersSelected one of
* LOG_BUFFER_MAIN = 1 << 0;
* LOG_BUFFER_SYSTEM = 1 << 1;
* LOG_BUFFER_RADIO = 1 << 2;
* LOG_BUFFER_EVENTS = 1 << 3;
* LOG_BUFFER_KERNEL = 1 << 4;
* @return the same Builder
*/
public Builder setLogBufferSelected(int logBuffersSelected) {
this.mLogBuffersSelected = logBuffersSelected;
return this;
}
/**
* sets log out format, -v parameter
*
* @param logOutFormat out format, like -v time
* @return the same Builder
*/
public Builder setLogOutFormat(int logOutFormat) {
this.mLogOutFormat = mLogOutFormat;
return this;
}
public Builder(Context context) {
mContext = context;
}
/**
* call this only if mLogFolderName and mLogFolderPath not
* be set both.
*
* @return
*/
private void applyAppNameAsOutfolderName() {
try {
String appName = mContext.getPackageName();
String versionName = mContext.getPackageManager().getPackageInfo(
appName, 0).versionName;
int versionCode = mContext.getPackageManager()
.getPackageInfo(appName, 0).versionCode;
mLogFolderName = appName + "-" + versionName + "-" + versionCode;
mLogFolderPath = applyOutfolderPath();
} catch (Exception e) {
}
}
private String applyOutfolderPath() {
String outPath = "";
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
outPath = Environment.getExternalStorageDirectory()
.getAbsolutePath() + File.separator + mLogFolderName;
}
return outPath;
}
/**
* Combine all of the options that have been set and return
* a new {@link LogRecorder} object.
*/
public LogRecorder build() {
LogRecorder logRecorder = new LogRecorder();
// no folder name & folder path be set
if (TextUtils.isEmpty(mLogFolderName)
&& TextUtils.isEmpty(mLogFolderPath)) {
applyAppNameAsOutfolderName();
}
// make sure out path be set
if (TextUtils.isEmpty(mLogFolderPath)) {
mLogFolderPath = applyOutfolderPath();
}
logRecorder.mFolderPath = mLogFolderPath;
logRecorder.mFileSuffix = mLogFileNameSuffix;
logRecorder.mFileSizeLimitation = mLogFileSizeLimitation;
logRecorder.mLevel = mLogLevel;
if (!mLogFilterTags.isEmpty()) {
for (int i = 0; i < mLogFilterTags.size(); i++) {
logRecorder.mFilterTags.add(mLogFilterTags.get(i));
}
}
logRecorder.mPID = mPID;
return logRecorder;
}
}
}

View File

@@ -1,63 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Represents an in-app billing purchase.
*/
public class Purchase {
String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS
String mOrderId;
String mPackageName;
String mSku;
long mPurchaseTime;
int mPurchaseState;
String mDeveloperPayload;
String mToken;
String mOriginalJson;
String mSignature;
public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException {
mItemType = itemType;
mOriginalJson = jsonPurchaseInfo;
JSONObject o = new JSONObject(mOriginalJson);
mOrderId = o.optString("orderId");
mPackageName = o.optString("packageName");
mSku = o.optString("productId");
mPurchaseTime = o.optLong("purchaseTime");
mPurchaseState = o.optInt("purchaseState");
mDeveloperPayload = o.optString("developerPayload");
mToken = o.optString("token", o.optString("purchaseToken"));
mSignature = signature;
}
public String getItemType() { return mItemType; }
public String getOrderId() { return mOrderId; }
public String getPackageName() { return mPackageName; }
public String getSku() { return mSku; }
public long getPurchaseTime() { return mPurchaseTime; }
public int getPurchaseState() { return mPurchaseState; }
public String getDeveloperPayload() { return mDeveloperPayload; }
public String getToken() { return mToken; }
public String getOriginalJson() { return mOriginalJson; }
public String getSignature() { return mSignature; }
@Override
public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; }
}

View File

@@ -1,119 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
import android.text.TextUtils;
import android.util.Log;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Security-related methods. For a secure implementation, all of this code
* should be implemented on a server that communicates with the
* application on the device. For the sake of simplicity and clarity of this
* example, this code is included here and is executed on the device. If you
* must verify the purchases on the phone, you should obfuscate this code to
* make it harder for an attacker to replace the code with stubs that treat all
* purchases as verified.
*/
public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) ||
TextUtils.isEmpty(signature)) {
Log.e(TAG, "Purchase verification failed: missing data.");
return false;
}
PublicKey key = Security.generatePublicKey(base64PublicKey);
return Security.verify(key, signedData, signature);
}
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}
}

View File

@@ -1,58 +0,0 @@
/* Copyright (c) 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.v2ray.ang.util;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Represents an in-app product's listing details.
*/
public class SkuDetails {
String mItemType;
String mSku;
String mType;
String mPrice;
String mTitle;
String mDescription;
String mJson;
public SkuDetails(String jsonSkuDetails) throws JSONException {
this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails);
}
public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException {
mItemType = itemType;
mJson = jsonSkuDetails;
JSONObject o = new JSONObject(mJson);
mSku = o.optString("productId");
mType = o.optString("type");
mPrice = o.optString("price");
mTitle = o.optString("title");
mDescription = o.optString("description");
}
public String getSku() { return mSku; }
public String getType() { return mType; }
public String getPrice() { return mPrice; }
public String getTitle() { return mTitle; }
public String getDescription() { return mDescription; }
@Override
public String toString() {
return "SkuDetails:" + mJson;
}
}

BIN
V2rayNG/app/src/main/jniLibs/arm64-v8a/libtun2socks.so Normal file → Executable file

Binary file not shown.

BIN
V2rayNG/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so Normal file → Executable file

Binary file not shown.

BIN
V2rayNG/app/src/main/jniLibs/x86/libtun2socks.so Normal file → Executable file

Binary file not shown.

BIN
V2rayNG/app/src/main/jniLibs/x86_64/libtun2socks.so Normal file → Executable file

Binary file not shown.

View File

@@ -1,9 +1,8 @@
package com.v2ray.ang
import android.support.multidex.MultiDexApplication
import android.support.v7.preference.PreferenceManager
import com.v2ray.ang.util.AngConfigManager
import me.dozen.dpreference.DPreference
import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import com.tencent.mmkv.MMKV
class AngApplication : MultiDexApplication() {
companion object {
@@ -14,8 +13,6 @@ class AngApplication : MultiDexApplication() {
var firstRun = false
private set
val defaultDPreference by lazy { DPreference(this, packageName + "_preferences") }
override fun onCreate() {
super.onCreate()
@@ -27,6 +24,6 @@ class AngApplication : MultiDexApplication() {
defaultSharedPreferences.edit().putInt(PREF_LAST_VERSION, BuildConfig.VERSION_CODE).apply()
//Logger.init().logLevel(if (BuildConfig.DEBUG) LogLevel.FULL else LogLevel.NONE)
AngConfigManager.inject(this)
MMKV.initialize(this)
}
}

View File

@@ -6,18 +6,46 @@ package com.v2ray.ang
*/
object AppConfig {
const val ANG_PACKAGE = "com.v2ray.ang"
const val ANG_CONFIG = "ang_config"
const val PREF_CURR_CONFIG = "pref_v2ray_config"
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://"
// legacy
const val ANG_CONFIG = "ang_config"
const val PREF_INAPP_BUY_IS_PREMIUM = "pref_inapp_buy_is_premium"
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
// Preferences mapped to MMKV
const val PREF_MODE = "pref_mode"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_FAKE_DNS_ENABLED = "pref_fake_dns_enabled"
const val PREF_VPN_DNS = "pref_vpn_dns"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
const val PREF_LOCAL_DNS_PORT = "pref_local_dns_port"
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_MODE = "pref_routing_mode"
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps"
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
// const val PREF_SOCKS_PORT = "pref_socks_port"
// const val PREF_HTTP_PORT = "pref_http_port"
// const val PREF_DONATE = "pref_donate"
// const val PREF_LICENSES = "pref_licenses"
// const val PREF_FEEDBACK = "pref_feedback"
// const val PREF_TG_GROUP = "pref_tg_group"
// const val PREF_AUTO_RESTART = "pref_auto_restart"
const val HTTP_PROTOCOL: String = "http://"
const val HTTPS_PROTOCOL: String = "https://"
const val BROADCAST_ACTION_SERVICE = "com.v2ray.ang.action.service"
const val BROADCAST_ACTION_ACTIVITY = "com.v2ray.ang.action.activity"
const val BROADCAST_ACTION_WIDGET_CLICK = "com.v2ray.ang.action.widget.click"
@@ -28,9 +56,6 @@ object AppConfig {
const val TASKER_EXTRA_BUNDLE_GUID = "tasker_extra_bundle_guid"
const val TASKER_DEFAULT_GUID = "Default"
const val PREF_V2RAY_ROUTING_AGENT = "pref_v2ray_routing_agent"
const val PREF_V2RAY_ROUTING_DIRECT = "pref_v2ray_routing_direct"
const val PREF_V2RAY_ROUTING_BLOCKED = "pref_v2ray_routing_blocked"
const val TAG_AGENT = "proxy"
const val TAG_DIRECT = "direct"
const val TAG_BLOCKED = "block"

View File

@@ -1,12 +1,14 @@
package com.v2ray.ang.dto
enum class EConfigType(val value: Int) {
VMESS(1),
CUSTOM(2),
SHADOWSOCKS(3),
SOCKS(4);
enum class EConfigType(val value: Int, val protocolScheme: String) {
VMESS(1, "vmess://"),
CUSTOM(2, ""),
SHADOWSOCKS(3, "ss://"),
SOCKS(4, "socks://"),
VLESS(5, "vless://"),
TROJAN(6, "trojan://");
companion object {
fun fromInt(value: Int) = values().firstOrNull { it.value == value }
}
}
}

View File

@@ -0,0 +1,10 @@
package com.v2ray.ang.dto
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
fun getTestDelayString(): String {
if (testDelayMillis == 0L) {
return ""
}
return testDelayMillis.toString() + "ms"
}
}

View File

@@ -0,0 +1,69 @@
package com.v2ray.ang.dto
import com.v2ray.ang.AppConfig.TAG_AGENT
import com.v2ray.ang.AppConfig.TAG_BLOCKED
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.util.Utils
data class ServerConfig(
val configVersion: Int = 3,
val configType: EConfigType,
var subscriptionId: String = "",
val addedTime: Long = System.currentTimeMillis(),
var remarks: String = "",
val outboundBean: V2rayConfig.OutboundBean? = null,
var fullConfig: V2rayConfig? = null
) {
companion object {
fun create(configType: EConfigType): ServerConfig {
when(configType) {
EConfigType.VMESS, EConfigType.VLESS ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
vnext = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean(
users = listOf(V2rayConfig.OutboundBean.OutSettingsBean.VnextBean.UsersBean())))),
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
EConfigType.CUSTOM ->
return ServerConfig(configType = configType)
EConfigType.SHADOWSOCKS, EConfigType.SOCKS, EConfigType.TROJAN ->
return ServerConfig(
configType = configType,
outboundBean = V2rayConfig.OutboundBean(
protocol = configType.name.lowercase(),
settings = V2rayConfig.OutboundBean.OutSettingsBean(
servers = listOf(V2rayConfig.OutboundBean.OutSettingsBean.ServersBean())),
streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean()))
}
}
}
fun getProxyOutbound(): V2rayConfig.OutboundBean? {
if (configType != EConfigType.CUSTOM) {
return outboundBean
}
return fullConfig?.getProxyOutbound()
}
fun getAllOutboundTags(): MutableList<String> {
if (configType != EConfigType.CUSTOM) {
return mutableListOf(TAG_AGENT, TAG_DIRECT, TAG_BLOCKED)
}
fullConfig?.let { config ->
return config.outbounds.map { it.tag }.toMutableList()
}
return mutableListOf()
}
fun getV2rayPointDomainAndPort(): String {
val address = getProxyOutbound()?.getServerAddress().orEmpty()
val port = getProxyOutbound()?.getServerPort()
return if (Utils.isIpv6Address(address)) {
String.format("[%s]:%s", address, port)
} else {
String.format("%s:%s", address, port)
}
}
}

View File

@@ -0,0 +1,8 @@
package com.v2ray.ang.dto
data class SubscriptionItem(
var remarks: String = "",
var url: String = "",
var enabled: Boolean = true,
val addedTime: Long = System.currentTimeMillis()) {
}

View File

@@ -1,159 +1,446 @@
package com.v2ray.ang.dto
import android.text.TextUtils
import com.google.gson.GsonBuilder
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.annotations.SerializedName
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
data class V2rayConfig(
val stats: Any?=null,
var stats: Any? = null,
val log: LogBean,
val policy: PolicyBean,
var policy: PolicyBean?,
val inbounds: ArrayList<InboundBean>,
var outbounds: ArrayList<OutboundBean>,
var dns: DnsBean,
val routing: RoutingBean) {
val routing: RoutingBean,
val api: Any? = null,
val transport: Any? = null,
val reverse: Any? = null,
var fakedns: Any? = null,
val browserForwarder: Any? = null) {
companion object {
const val DEFAULT_PORT = 443
const val DEFAULT_SECURITY = "auto"
const val DEFAULT_LEVEL = 8
const val DEFAULT_NETWORK = "tcp"
const val DEFAULT_FLOW = "xtls-rprx-splice"
const val TLS = "tls"
const val XTLS = "xtls"
const val HTTP = "http"
}
data class LogBean(val access: String,
val error: String,
val loglevel: String)
var loglevel: String?,
val dnsLog: Boolean? = null)
data class InboundBean(
var tag: String,
var port: Int,
var protocol: String,
var listen: String?=null,
val settings: InSettingsBean,
val sniffing: SniffingBean?) {
var listen: String? = null,
val settings: Any? = null,
val sniffing: SniffingBean?,
val streamSettings: Any? = null,
val allocate: Any? = null) {
data class InSettingsBean(val auth: String? = null,
val udp: Boolean? = null,
val userLevel: Int? =null,
val userLevel: Int? = null,
val address: String? = null,
val port: Int? = null,
val network: String? = null)
data class SniffingBean(var enabled: Boolean,
val destOverride: List<String>)
val destOverride: ArrayList<String>,
val metadataOnly: Boolean? = null)
}
data class OutboundBean(val tag: String,
data class OutboundBean(val tag: String = "proxy",
var protocol: String,
var settings: OutSettingsBean?,
var streamSettings: StreamSettingsBean?,
var mux: MuxBean?) {
var settings: OutSettingsBean? = null,
var streamSettings: StreamSettingsBean? = null,
val proxySettings: Any? = null,
val sendThrough: String? = null,
val mux: MuxBean? = MuxBean(false)) {
data class OutSettingsBean(var vnext: List<VnextBean>?,
var servers: List<ServersBean>?,
var response: Response) {
data class OutSettingsBean(var vnext: List<VnextBean>? = null,
var servers: List<ServersBean>? = null,
/*Blackhole*/
var response: Response? = null,
/*DNS*/
val network: String? = null,
val address: String? = null,
val port: Int? = null,
/*Freedom*/
var domainStrategy: String? = null,
val redirect: String? = null,
val userLevel: Int? = null,
/*Loopback*/
val inboundTag: String? = null) {
data class VnextBean(var address: String,
var port: Int,
data class VnextBean(var address: String = "",
var port: Int = DEFAULT_PORT,
var users: List<UsersBean>) {
data class UsersBean(var id: String,
var alterId: Int,
var security: String,
var level: Int)
data class UsersBean(var id: String = "",
var security: String = DEFAULT_SECURITY,
var level: Int = DEFAULT_LEVEL,
var encryption: String = "",
var flow: String = "")
}
data class ServersBean(var address: String,
var method: String,
var ota: Boolean,
var password: String,
var port: Int,
var level: Int)
data class ServersBean(var address: String = "",
var method: String = "chacha20-poly1305",
var ota: Boolean = false,
var password: String = "",
var port: Int = DEFAULT_PORT,
var level: Int = DEFAULT_LEVEL,
val email: String? = null,
val flow: String? = null,
val ivCheck: Boolean? = null,
var users: List<SocksUsersBean>? = null) {
data class SocksUsersBean(var user: String = "",
var pass: String = "",
var level: Int = DEFAULT_LEVEL)
}
data class Response(var type: String)
}
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?
data class StreamSettingsBean(var network: String = DEFAULT_NETWORK,
var security: String = "",
var tcpSettings: TcpSettingsBean? = null,
var kcpSettings: KcpSettingsBean? = null,
var wsSettings: WsSettingsBean? = null,
var httpSettings: HttpSettingsBean? = null,
var tlsSettings: TlsSettingsBean? = null,
var quicSettings: QuicSettingBean? = null,
var xtlsSettings: TlsSettingsBean? = null,
var grpcSettings: GrpcSettingsBean? = null,
val dsSettings: Any? = null,
val sockopt: Any? = null
) {
data class TcpSettingsBean(var header: HeaderBean = HeaderBean()) {
data class TcpSettingsBean(var header: HeaderBean = HeaderBean(),
val acceptProxyProtocol: Boolean? = null) {
data class HeaderBean(var type: String = "none",
var request: Any? = null,
var response: Any? = null)
var request: RequestBean? = null,
var response: Any? = null) {
data class RequestBean(var path: List<String> = ArrayList(),
var headers: HeadersBean = HeadersBean(),
val version: String? = null,
val method: String? = null) {
data class HeadersBean(var Host: List<String> = ArrayList(),
@SerializedName("User-Agent")
val userAgent: List<String>? = null,
@SerializedName("Accept-Encoding")
val acceptEncoding: List<String>? = null,
val Connection: List<String>? = null,
val Pragma: String? = null)
}
}
}
data class KcpSettingsBean(var mtu: Int = 1350,
var tti: Int = 20,
var tti: Int = 50,
var uplinkCapacity: Int = 12,
var downlinkCapacity: Int = 100,
var congestion: Boolean = false,
var readBufferSize: Int = 1,
var writeBufferSize: Int = 1,
var header: HeaderBean = HeaderBean()) {
var header: HeaderBean = HeaderBean(),
var seed: String? = null) {
data class HeaderBean(var type: String = "none")
}
data class WsSettingsBean(var path: String = "",
var headers: HeadersBean = HeadersBean()) {
var headers: HeadersBean = HeadersBean(),
val maxEarlyData: Int? = null,
val useBrowserForwarding: Boolean? = null,
val acceptProxyProtocol: Boolean? = null) {
data class HeadersBean(var Host: String = "")
}
data class HttpSettingsBean(var host: List<String> = ArrayList(),
var path: String = "")
data class TlsSettingsBean(var allowInsecure: Boolean = true,
var serverName: String = "")
data class TlsSettingsBean(var allowInsecure: Boolean = false,
var serverName: String = "",
val alpn: List<String>? = null,
val minVersion: String? = null,
val maxVersion: String? = null,
val preferServerCipherSuites: Boolean? = null,
val cipherSuites: String? = null,
val fingerprint: String? = null,
val certificates: List<Any>? = null,
val disableSystemRoot: Boolean? = null,
val enableSessionResumption: Boolean? = null)
data class QuicSettingBean(var security: String = "none",
var key: String = "",
var header: HeaderBean = HeaderBean()) {
data class HeaderBean(var type: String = "none")
}
data class GrpcSettingsBean(var serviceName: String = "",
var multiMode: Boolean? = null)
fun populateTransportSettings(transport: String, headerType: String?, host: String?, path: String?, seed: String?,
quicSecurity: String?, key: String?, mode: String?, serviceName: String?): String {
var sni = ""
network = transport
when (network) {
"tcp" -> {
val tcpSetting = TcpSettingsBean()
if (headerType == HTTP) {
tcpSetting.header.type = HTTP
if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(path)) {
val requestObj = TcpSettingsBean.HeaderBean.RequestBean()
requestObj.headers.Host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
requestObj.path = (path ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
tcpSetting.header.request = requestObj
sni = requestObj.headers.Host.getOrNull(0) ?: sni
}
} else {
tcpSetting.header.type = "none"
sni = host ?: ""
}
tcpSettings = tcpSetting
}
"kcp" -> {
val kcpsetting = KcpSettingsBean()
kcpsetting.header.type = headerType ?: "none"
if (seed.isNullOrEmpty()) {
kcpsetting.seed = null
} else {
kcpsetting.seed = seed
}
kcpSettings = kcpsetting
}
"ws" -> {
val wssetting = WsSettingsBean()
wssetting.headers.Host = host ?: ""
sni = wssetting.headers.Host
wssetting.path = path ?: "/"
wsSettings = wssetting
}
"h2", "http" -> {
network = "h2"
val h2Setting = HttpSettingsBean()
h2Setting.host = (host ?: "").split(",").map { it.trim() }.filter { it.isNotEmpty() }
sni = h2Setting.host.getOrNull(0) ?: sni
h2Setting.path = path ?: "/"
httpSettings = h2Setting
}
"quic" -> {
val quicsetting = QuicSettingBean()
quicsetting.security = quicSecurity ?: "none"
quicsetting.key = key ?: ""
quicsetting.header.type = headerType ?: "none"
quicSettings = quicsetting
}
"grpc" -> {
val grpcSetting = GrpcSettingsBean()
grpcSetting.multiMode = mode == "multi"
grpcSetting.serviceName = serviceName ?: ""
sni = host ?: ""
grpcSettings = grpcSetting
}
}
return sni
}
fun populateTlsSettings(streamSecurity: String, allowInsecure: Boolean, sni: String) {
security = streamSecurity
val tlsSetting = TlsSettingsBean(
allowInsecure = allowInsecure,
serverName = sni
)
if (security == TLS) {
tlsSettings = tlsSetting
} else if (security == XTLS) {
xtlsSettings = tlsSetting
}
}
}
data class MuxBean(var enabled: Boolean)
data class MuxBean(var enabled: Boolean, var concurrency: Int = 8)
fun getServerAddress(): String? {
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.address
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) {
return settings?.servers?.get(0)?.address
}
return null
}
fun getServerPort(): Int? {
if (protocol.equals(EConfigType.VMESS.name.toLowerCase())) {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.port
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name.toLowerCase()) || protocol.equals(EConfigType.SOCKS.name.toLowerCase())) {
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.SOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) {
return settings?.servers?.get(0)?.port
}
return null
}
fun getPassword(): String? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
return settings?.vnext?.get(0)?.users?.get(0)?.id
} else if (protocol.equals(EConfigType.SHADOWSOCKS.name, true)
|| protocol.equals(EConfigType.TROJAN.name, true)) {
return settings?.servers?.get(0)?.password
} else if (protocol.equals(EConfigType.SOCKS.name, true)) {
return settings?.servers?.get(0)?.users?.get(0)?.pass
}
return null
}
fun getSecurityEncryption(): String? {
return when {
protocol.equals(EConfigType.VMESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.security
protocol.equals(EConfigType.VLESS.name, true) -> settings?.vnext?.get(0)?.users?.get(0)?.encryption
protocol.equals(EConfigType.SHADOWSOCKS.name, true) -> settings?.servers?.get(0)?.method
else -> null
}
}
fun getTransportSettingDetails(): List<String>? {
if (protocol.equals(EConfigType.VMESS.name, true)
|| protocol.equals(EConfigType.VLESS.name, true)) {
val transport = streamSettings?.network ?: return null
return when (transport) {
"tcp" -> {
val tcpSetting = streamSettings?.tcpSettings ?: return null
listOf(tcpSetting.header.type,
tcpSetting.header.request?.headers?.Host?.joinToString().orEmpty(),
tcpSetting.header.request?.path?.joinToString().orEmpty())
}
"kcp" -> {
val kcpSetting = streamSettings?.kcpSettings ?: return null
listOf(kcpSetting.header.type,
"",
kcpSetting.seed.orEmpty())
}
"ws" -> {
val wsSetting = streamSettings?.wsSettings ?: return null
listOf("",
wsSetting.headers.Host,
wsSetting.path)
}
"h2" -> {
val h2Setting = streamSettings?.httpSettings ?: return null
listOf("",
h2Setting.host.joinToString(),
h2Setting.path)
}
"quic" -> {
val quicSetting = streamSettings?.quicSettings ?: return null
listOf(quicSetting.header.type,
quicSetting.security,
quicSetting.key)
}
"grpc" -> {
val grpcSetting = streamSettings?.grpcSettings ?: return null
listOf(if (grpcSetting.multiMode == true) "multi" else "gun",
"",
grpcSetting.serviceName)
}
else -> null
}
}
return null
}
}
//data class DnsBean(var servers: List<String>)
data class DnsBean(var servers: List<Any>?=null,
var hosts: Map<String, String>?=null
data class DnsBean(var servers: ArrayList<Any>? = null,
var hosts: Map<String, Any>? = null,
val clientIp: String? = null,
val disableCache: Boolean? = null,
val queryStrategy: String? = null,
val tag: String? = null
) {
data class ServersBean(var address: String = "",
var port: Int = 0,
var domains: List<String>?)
var port: Int? = null,
var domains: List<String>? = null,
var expectIPs: List<String>? = null,
val clientIp: String? = null)
}
data class RoutingBean(var domainStrategy: String,
var rules: ArrayList<RulesBean>) {
val domainMatcher: String? = null,
var rules: ArrayList<RulesBean>,
val balancers: List<Any>? = null) {
data class RulesBean(var type: String = "",
var ip: ArrayList<String>? = null,
var domain: ArrayList<String>? = null,
var outboundTag: String = "",
var balancerTag: String? = null,
var port: String? = null,
var inboundTag: ArrayList<String>? = null)
val sourcePort: String? = null,
val network: String? = null,
val source: List<String>? = null,
val user: List<String>? = null,
var inboundTag: List<String>? = null,
val protocol: List<String>? = null,
val attrs: String? = null,
val domainMatcher: String? = null
)
}
data class PolicyBean(var levels: Map<String, LevelBean>,
var system: Any?=null) {
var system: Any? = null) {
data class LevelBean(
var handshake: Int? = null,
var connIdle: Int? = null,
var uplinkOnly: Int? = null,
var downlinkOnly: Int? = null)
var handshake: Int? = null,
var connIdle: Int? = null,
var uplinkOnly: Int? = null,
var downlinkOnly: Int? = null,
val statsUserUplink: Boolean? = null,
val statsUserDownlink: Boolean? = null,
var bufferSize: Int? = null)
}
}
data class FakednsBean(var ipPool: String = "198.18.0.0/15",
var poolSize: Int = 10000) // roughly 10 times smaller than total ip pool
fun getProxyOutbound(): OutboundBean? {
outbounds.forEach { outbound ->
if (outbound.protocol.equals(EConfigType.VMESS.name, true) ||
outbound.protocol.equals(EConfigType.VLESS.name, true) ||
outbound.protocol.equals(EConfigType.SHADOWSOCKS.name, true) ||
outbound.protocol.equals(EConfigType.SOCKS.name, true) ||
outbound.protocol.equals(EConfigType.TROJAN.name, true)) {
return outbound
}
}
return null
}
fun toPrettyPrinting(): String {
return GsonBuilder()
.setPrettyPrinting()
.disableHtmlEscaping()
.registerTypeAdapter( // custom serialiser is needed here since JSON by default parse number as Double, core will fail to start
object : TypeToken<Double>() {}.type,
JsonSerializer { src: Double?, _: Type?, _: JsonSerializationContext? -> JsonPrimitive(src?.toInt()) }
)
.create()
.toJson(this)
}
}

View File

@@ -5,9 +5,10 @@ data class VmessQRCode(var v: String = "",
var add: String = "",
var port: String = "",
var id: String = "",
var aid: String = "",
var aid: String = "0",
var net: String = "",
var type: String = "",
var host: String = "",
var path: String = "",
var tls: String = "")
var tls: String = "",
var sni: String = "")

View File

@@ -1,244 +0,0 @@
package com.v2ray.ang.extension
import android.app.Fragment
import android.app.ProgressDialog
import android.content.Context
import android.content.DialogInterface
import android.database.Cursor
import android.graphics.drawable.Drawable
import android.support.v7.app.AlertDialog
import android.view.KeyEvent
import android.view.View
import android.widget.ListAdapter
fun Context.alertView(
title: String? = null,
view: View,
init: (KAlertDialogBuilder.() -> Unit)? = null
) = KAlertDialogBuilder(this).apply {
if (title != null) title(title)
if (title != null) customView(view)
if (init != null) init()
}
fun Fragment.alert(
message: String,
title: String? = null,
init: (KAlertDialogBuilder.() -> Unit)? = null
) = activity.alert(message, title, init)
fun Context.alert(
message: String,
title: String? = null,
init: (KAlertDialogBuilder.() -> Unit)? = null
) = KAlertDialogBuilder(this).apply {
if (title != null) title(title)
message(message)
if (init != null) init()
}
fun Fragment.alert(
message: Int,
title: Int? = null,
init: (KAlertDialogBuilder.() -> Unit)? = null
) = activity.alert(message, title, init)
fun Context.alert(
message: Int,
title: Int? = null,
init: (KAlertDialogBuilder.() -> Unit)? = null
) = KAlertDialogBuilder(this).apply {
if (title != null) title(title)
message(message)
if (init != null) init()
}
fun Fragment.alert(init: KAlertDialogBuilder.() -> Unit): KAlertDialogBuilder = activity.alert(init)
fun Context.alert(init: KAlertDialogBuilder.() -> Unit) = KAlertDialogBuilder(this).apply { init() }
fun Fragment.progressDialog(
message: Int? = null,
title: Int? = null,
init: (ProgressDialog.() -> Unit)? = null
) = activity.progressDialog(message, title, init)
fun Context.progressDialog(
message: Int? = null,
title: Int? = null,
init: (ProgressDialog.() -> Unit)? = null
) = progressDialog(false, message?.let { getString(it) }, title?.let { getString(it) }, init)
fun Fragment.indeterminateProgressDialog(
message: Int? = null,
title: Int? = null,
init: (ProgressDialog.() -> Unit)? = null
) = activity.progressDialog(message, title, init)
fun Context.indeterminateProgressDialog(
message: Int? = null,
title: Int? = null,
init: (ProgressDialog.() -> Unit)? = null
) = progressDialog(true, message?.let { getString(it) }, title?.let { getString(it) }, init)
fun Fragment.progressDialog(
message: String? = null,
title: String? = null,
init: (ProgressDialog.() -> Unit)? = null
) = activity.progressDialog(message, title, init)
fun Context.progressDialog(
message: String? = null,
title: String? = null,
init: (ProgressDialog.() -> Unit)? = null
) = progressDialog(false, message, title, init)
fun Fragment.indeterminateProgressDialog(
message: String? = null,
title: String? = null,
init: (ProgressDialog.() -> Unit)? = null
) = activity.indeterminateProgressDialog(message, title, init)
fun Context.indeterminateProgressDialog(
message: String? = null,
title: String? = null,
init: (ProgressDialog.() -> Unit)? = null
) = progressDialog(true, message, title, init)
private fun Context.progressDialog(
indeterminate: Boolean,
message: String? = null,
title: String? = null,
init: (ProgressDialog.() -> Unit)? = null
) = ProgressDialog(this).apply {
isIndeterminate = indeterminate
if (!indeterminate) setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)
if (message != null) setMessage(message)
if (title != null) setTitle(title)
if (init != null) init()
show()
}
fun Fragment.selector(
title: CharSequence? = null,
items: List<CharSequence>,
onClick: (Int) -> Unit
): Unit = activity.selector(title, items, onClick)
fun Context.selector(
title: CharSequence? = null,
items: List<CharSequence>,
onClick: (Int) -> Unit
) {
with(KAlertDialogBuilder(this)) {
if (title != null) title(title)
items(items, onClick)
show()
}
}
class KAlertDialogBuilder(val ctx: Context) {
val builder: AlertDialog.Builder = AlertDialog.Builder(ctx)
protected var dialog: AlertDialog? = null
fun dismiss() {
dialog?.dismiss()
}
fun show(): KAlertDialogBuilder {
dialog = builder.create()
dialog!!.show()
return this
}
fun title(title: CharSequence) {
builder.setTitle(title)
}
fun title(resource: Int) {
builder.setTitle(resource)
}
fun message(title: CharSequence) {
builder.setMessage(title)
}
fun message(resource: Int) {
builder.setMessage(resource)
}
fun icon(icon: Int) {
builder.setIcon(icon)
}
fun icon(icon: Drawable) {
builder.setIcon(icon)
}
fun customTitle(title: View) {
builder.setCustomTitle(title)
}
fun customView(view: View) {
builder.setView(view)
}
fun cancellable(value: Boolean = true) {
builder.setCancelable(value)
}
fun onCancel(f: () -> Unit) {
builder.setOnCancelListener { f() }
}
fun onKey(f: (keyCode: Int, e: KeyEvent) -> Boolean) {
builder.setOnKeyListener({ dialog, keyCode, event -> f(keyCode, event) })
}
fun neutralButton(textResource: Int = android.R.string.ok, f: DialogInterface.() -> Unit = { dismiss() }) {
neutralButton(ctx.getString(textResource), f)
}
fun neutralButton(title: String, f: DialogInterface.() -> Unit = { dismiss() }) {
builder.setNeutralButton(title, { dialog, which -> dialog.f() })
}
fun positiveButton(textResource: Int = android.R.string.ok, f: DialogInterface.() -> Unit) {
positiveButton(ctx.getString(textResource), f)
}
fun positiveButton(title: String, f: DialogInterface.() -> Unit) {
builder.setPositiveButton(title, { dialog, which -> dialog.f() })
}
fun negativeButton(textResource: Int = android.R.string.cancel, f: DialogInterface.() -> Unit = { dismiss() }) {
negativeButton(ctx.getString(textResource), f)
}
fun negativeButton(title: String, f: DialogInterface.() -> Unit = { dismiss() }) {
builder.setNegativeButton(title, { dialog, which -> dialog.f() })
}
fun items(itemsId: Int, f: (which: Int) -> Unit) {
items(ctx.resources!!.getTextArray(itemsId), f)
}
fun items(items: List<CharSequence>, f: (which: Int) -> Unit) {
items(items.toTypedArray(), f)
}
fun items(items: Array<CharSequence>, f: (which: Int) -> Unit) {
builder.setItems(items, { dialog, which -> f(which) })
}
fun adapter(adapter: ListAdapter, f: (which: Int) -> Unit) {
builder.setAdapter(adapter, { dialog, which -> f(which) })
}
fun adapter(cursor: Cursor, labelColumn: String, f: (which: Int) -> Unit) {
builder.setCursor(cursor, { dialog, which -> f(which) }, labelColumn)
}
}

View File

@@ -4,7 +4,7 @@ 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
@@ -15,22 +15,19 @@ import java.net.URLConnection
val Context.v2RayApplication: AngApplication
get() = applicationContext as AngApplication
val Context.defaultDPreference: DPreference
get() = v2RayApplication.defaultDPreference
inline fun Context.toast(message: Int): Toast = Toast
fun Context.toast(message: Int): Toast = ToastCompat
.makeText(this, message, Toast.LENGTH_SHORT)
.apply {
show()
}
inline fun Context.toast(message: CharSequence): Toast = Toast
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(pair: Pair<String, Any>) = putOpt(pair.first, pair.second)
fun JSONObject.putOpt(pairs: Map<String, Any>) = pairs.forEach { putOpt(it.key to it.value) }
const val threshold = 1000

View File

@@ -1,10 +0,0 @@
package com.v2ray.ang.extension
import android.preference.Preference
fun Preference.onClick(listener: () -> Unit) {
setOnPreferenceClickListener {
listener()
true
}
}

View File

@@ -5,11 +5,16 @@ import android.content.Context
import android.content.Intent
import android.text.TextUtils
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
class TaskerReceiver : BroadcastReceiver() {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
override fun onReceive(context: Context, intent: Intent?) {
try {
@@ -21,9 +26,10 @@ class TaskerReceiver : BroadcastReceiver() {
return
} else if (switch) {
if (guid == AppConfig.TASKER_DEFAULT_GUID) {
Utils.startVService(context)
Utils.startVServiceFromToggle(context)
} else {
Utils.startVService(context, guid)
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
V2RayServiceManager.startV2Ray(context)
}
} else {
Utils.stopVService(context)

View File

@@ -6,6 +6,7 @@ import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.widget.RemoteViews
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig
@@ -21,16 +22,33 @@ class WidgetProvider : AppWidgetProvider() {
updateWidgetBackground(context, appWidgetManager, appWidgetIds, V2RayServiceManager.v2rayPoint.isRunning)
}
private fun updateWidgetBackground(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, isRunning: Boolean) {
val remoteViews = RemoteViews(context.packageName, R.layout.widget_switch)
val intent = Intent(context, WidgetProvider::class.java)
intent.action = AppConfig.BROADCAST_ACTION_WIDGET_CLICK
val pendingIntent = PendingIntent.getBroadcast(context, R.id.layout_switch, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val pendingIntent = PendingIntent.getBroadcast(
context,
R.id.layout_switch,
intent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
})
remoteViews.setOnClickPendingIntent(R.id.layout_switch, pendingIntent)
if (isRunning) {
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_theme)
remoteViews.setInt(
R.id.layout_switch,
"setBackgroundResource",
R.drawable.ic_rounded_corner_theme
)
} else {
remoteViews.setInt(R.id.layout_switch, "setBackgroundResource", R.drawable.ic_rounded_corner_grey)
remoteViews.setInt(
R.id.layout_switch,
"setBackgroundResource",
R.drawable.ic_rounded_corner_grey
)
}
for (appWidgetId in appWidgetIds) {
@@ -50,15 +68,16 @@ class WidgetProvider : AppWidgetProvider() {
Utils.startVServiceFromToggle(context)
}
} else if (AppConfig.BROADCAST_ACTION_ACTIVITY == intent.action) {
val manager = AppWidgetManager.getInstance(context)
when (intent.getIntExtra("key", 0)) {
AppConfig.MSG_STATE_RUNNING, AppConfig.MSG_STATE_START_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
true)
}
AppConfig.MSG_STATE_NOT_RUNNING, AppConfig.MSG_STATE_START_FAILURE, AppConfig.MSG_STATE_STOP_SUCCESS -> {
updateWidgetBackground(context, manager, manager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java)),
false)
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

@@ -11,12 +11,10 @@ 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 java.lang.ref.SoftReference
@TargetApi(Build.VERSION_CODES.N)
class QSTileService : TileService() {
@@ -27,7 +25,7 @@ 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.currentConfig?.remarks
qsTile?.icon = Icon.createWithResource(applicationContext, R.drawable.ic_v)
}

View File

@@ -10,19 +10,26 @@ 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 androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.AppConfig.TAG_DIRECT
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.extension.toSpeedString
import com.v2ray.ang.extension.toast
import com.v2ray.ang.ui.MainActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MessageUtil
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.util.V2rayConfigUtil
import go.Seq
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import libv2ray.Libv2ray
import libv2ray.V2RayPoint
import libv2ray.V2RayVPNServiceSupportsSet
@@ -39,6 +46,8 @@ object V2RayServiceManager {
val v2rayPoint: V2RayPoint = Libv2ray.newV2RayPoint(V2RayCallback())
private val mMsgReceive = ReceiveMessageHandler()
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
var serviceControl: SoftReference<ServiceControl>? = null
set(value) {
@@ -50,14 +59,20 @@ object V2RayServiceManager {
Seq.setContext(context)
}
}
var currentConfig: ServerConfig? = null
private var lastQueryTime = 0L
private var mBuilder: NotificationCompat.Builder? = null
private var mSubscription: Subscription? = null
private var mNotificationManager: NotificationManager? = null
fun startV2Ray(context: Context, mode: String) {
val intent = if (mode == "VPN") {
fun startV2Ray(context: Context) {
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) == true) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{
context.toast(R.string.toast_services_start)
}
val intent = if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
Intent(context.applicationContext, V2RayVpnService::class.java)
} else {
Intent(context.applicationContext, V2RayProxyOnlyService::class.java)
@@ -78,7 +93,7 @@ object V2RayServiceManager {
serviceControl.stopService()
0
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
Log.d(ANG_PACKAGE, e.toString())
-1
}
}
@@ -106,7 +121,7 @@ object V2RayServiceManager {
startSpeedNotification()
0
} catch (e: Exception) {
Log.d(serviceControl.getService().packageName, e.toString())
Log.d(ANG_PACKAGE, e.toString())
-1
}
}
@@ -115,7 +130,12 @@ object V2RayServiceManager {
fun startV2rayPoint() {
val service = serviceControl?.get()?.getService() ?: return
val guid = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER) ?: return
val config = MmkvManager.decodeServerConfig(guid) ?: return
if (!v2rayPoint.isRunning) {
val result = V2rayConfigUtil.getV2rayConfig(service, guid)
if (!result.status)
return
try {
val mFilter = IntentFilter(AppConfig.BROADCAST_ACTION_SERVICE)
@@ -124,19 +144,20 @@ object V2RayServiceManager {
mFilter.addAction(Intent.ACTION_USER_PRESENT)
service.registerReceiver(mMsgReceive, mFilter)
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
Log.d(ANG_PACKAGE, 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"
v2rayPoint.configureFileContent = result.content
v2rayPoint.domainName = config.getV2rayPointDomainAndPort()
currentConfig = config
v2rayPoint.enableLocalDNS = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
v2rayPoint.forwardIpv6 = settingsStorage?.decodeBool(AppConfig.PREF_FORWARD_IPV6) ?: false
v2rayPoint.proxyOnly = settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" != "VPN"
try {
v2rayPoint.runLoop()
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
Log.d(ANG_PACKAGE, e.toString())
}
if (v2rayPoint.isRunning) {
@@ -153,10 +174,12 @@ object V2RayServiceManager {
val service = serviceControl?.get()?.getService() ?: return
if (v2rayPoint.isRunning) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
GlobalScope.launch(Dispatchers.Default) {
try {
v2rayPoint.stopLoop()
} catch (e: Exception) {
Log.d(ANG_PACKAGE, e.toString())
}
}
}
@@ -166,7 +189,7 @@ object V2RayServiceManager {
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.d(service.packageName, e.toString())
Log.d(ANG_PACKAGE, e.toString())
}
}
@@ -198,11 +221,11 @@ object V2RayServiceManager {
when (intent?.action) {
Intent.ACTION_SCREEN_OFF -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
Log.d(ANG_PACKAGE, "SCREEN_OFF, stop querying stats")
stopSpeedNotification()
}
Intent.ACTION_SCREEN_ON -> {
Log.d(AppConfig.ANG_PACKAGE, "SCREEN_ON, start querying stats")
Log.d(ANG_PACKAGE, "SCREEN_ON, start querying stats")
startSpeedNotification()
}
}
@@ -214,15 +237,23 @@ object V2RayServiceManager {
val startMainIntent = Intent(service, MainActivity::class.java)
val contentPendingIntent = PendingIntent.getActivity(service,
NOTIFICATION_PENDING_INTENT_CONTENT, startMainIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
})
val stopV2RayIntent = Intent(AppConfig.BROADCAST_ACTION_SERVICE)
stopV2RayIntent.`package` = AppConfig.ANG_PACKAGE
stopV2RayIntent.`package` = ANG_PACKAGE
stopV2RayIntent.putExtra("key", AppConfig.MSG_STATE_STOP)
val stopV2RayPendingIntent = PendingIntent.getBroadcast(service,
NOTIFICATION_PENDING_INTENT_STOP_V2RAY, stopV2RayIntent,
PendingIntent.FLAG_UPDATE_CURRENT)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
} else {
PendingIntent.FLAG_UPDATE_CURRENT
})
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -235,7 +266,7 @@ object V2RayServiceManager {
mBuilder = NotificationCompat.Builder(service, channelId)
.setSmallIcon(R.drawable.ic_v)
.setContentTitle(service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
.setContentTitle(currentConfig?.remarks)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setOngoing(true)
.setShowWhen(false)
@@ -272,7 +303,7 @@ object V2RayServiceManager {
mSubscription = null
}
private fun updateNotification(contentText: String, proxyTraffic: Long, directTraffic: Long) {
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)
@@ -296,13 +327,12 @@ object V2RayServiceManager {
}
fun startSpeedNotification() {
val service = serviceControl?.get()?.getService() ?: return
if (mSubscription == null &&
v2rayPoint.isRunning &&
service.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SPEED_ENABLED, false)) {
settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) == true) {
var lastZeroSpeed = false
val outboundTags = service.defaultDPreference.getPrefStringOrderedSet(AppConfig.PREF_CURR_CONFIG_OUTBOUND_TAGS, LinkedHashSet())
outboundTags.remove(TAG_DIRECT)
val outboundTags = currentConfig?.getAllOutboundTags()
outboundTags?.remove(TAG_DIRECT)
mSubscription = Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS)
.subscribe {
@@ -310,7 +340,7 @@ object V2RayServiceManager {
val sinceLastQueryInSeconds = (queryTime - lastQueryTime) / 1000.0
var proxyTotal = 0L
val text = StringBuilder()
outboundTags.forEach {
outboundTags?.forEach {
val up = v2rayPoint.queryStats(it, "uplink")
val down = v2rayPoint.queryStats(it, "downlink")
if (up + down > 0) {
@@ -323,7 +353,7 @@ object V2RayServiceManager {
val zeroSpeed = (proxyTotal == 0L && directUplink == 0L && directDownlink == 0L)
if (!zeroSpeed || !lastZeroSpeed) {
if (proxyTotal == 0L) {
appendSpeedString(text, outboundTags.firstOrNull(), 0.0, 0.0)
appendSpeedString(text, outboundTags?.firstOrNull(), 0.0, 0.0)
}
appendSpeedString(text, TAG_DIRECT, directUplink / sinceLastQueryInSeconds,
directDownlink / sinceLastQueryInSeconds)
@@ -346,13 +376,10 @@ object V2RayServiceManager {
}
fun stopSpeedNotification() {
val service = serviceControl?.get()?.getService() ?: return
if (mSubscription != null) {
mSubscription?.unsubscribe() //stop queryStats
mSubscription = null
val cfName = service.defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, "")
updateNotification(cfName, 0, 0)
updateNotification(currentConfig?.remarks, 0, 0)
}
}
}

View File

@@ -8,13 +8,12 @@ import android.net.*
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.StrictMode
import android.support.annotation.RequiresApi
import androidx.annotation.RequiresApi
import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.ui.PerAppProxyActivity
import com.v2ray.ang.ui.SettingsActivity
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -23,6 +22,8 @@ import java.io.File
import java.lang.ref.SoftReference
class V2RayVpnService : VpnService(), ServiceControl {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private lateinit var mInterface: ParcelFileDescriptor
/**
@@ -92,8 +93,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
// If the old interface has exactly the same parameters, use it!
// 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")
val enableLocalDns = settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) ?: false
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
parameters.split(" ")
.map { it.split(",") }
@@ -121,18 +122,19 @@ class V2RayVpnService : VpnService(), ServiceControl {
}
if(!enableLocalDns) {
Utils.getRemoteDnsServers(defaultDPreference)
.forEach {
builder.addDnsServer(it)
}
Utils.getVpnDnsServers()
.forEach {
if (Utils.isPureIpAddress(it)) {
builder.addDnsServer(it)
}
}
}
builder.setSession(defaultDPreference.getPrefString(AppConfig.PREF_CURR_CONFIG_NAME, ""))
builder.setSession(V2RayServiceManager.currentConfig?.remarks.orEmpty())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)) {
val apps = defaultDPreference.getPrefStringSet(PerAppProxyActivity.PREF_PER_APP_PROXY_SET, null)
val bypassApps = defaultDPreference.getPrefBoolean(PerAppProxyActivity.PREF_BYPASS_APPS, false)
if (settingsStorage?.decodeBool(AppConfig.PREF_PER_APP_PROXY) == true) {
val apps = settingsStorage?.decodeStringSet(AppConfig.PREF_PER_APP_PROXY_SET)
val bypassApps = settingsStorage?.decodeBool(AppConfig.PREF_BYPASS_APPS) ?: false
apps?.forEach {
try {
if (bypassApps)

View File

@@ -1,6 +1,6 @@
package com.v2ray.ang.ui
import android.support.v7.app.AppCompatActivity
import androidx.appcompat.app.AppCompatActivity
import android.view.MenuItem
abstract class BaseActivity : AppCompatActivity() {
@@ -11,4 +11,4 @@ abstract class BaseActivity : AppCompatActivity() {
}
else -> super.onOptionsItemSelected(item)
}
}
}

View File

@@ -1,199 +0,0 @@
package com.v2ray.ang.ui
import android.app.ActivityOptions
import android.app.FragmentManager
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.support.design.widget.NavigationView
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.app.ActionBarDrawerToggle
import android.support.v7.widget.Toolbar
import android.util.Log
import android.view.MenuItem
import android.view.View
import com.v2ray.ang.R
abstract class BaseDrawerActivity : BaseActivity() {
companion object {
private val TAG = "BaseDrawerActivity"
}
private var mToolbar: Toolbar? = null
private var mDrawerToggle: ActionBarDrawerToggle? = null
private var mDrawerLayout: DrawerLayout? = null
private var mToolbarInitialized: Boolean = false
private var mItemToOpenWhenDrawerCloses = -1
private val backStackChangedListener = FragmentManager.OnBackStackChangedListener { updateDrawerToggle() }
private val drawerListener = object : DrawerLayout.DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
mDrawerToggle!!.onDrawerSlide(drawerView, slideOffset)
}
override fun onDrawerOpened(drawerView: View) {
mDrawerToggle!!.onDrawerOpened(drawerView)
//supportActionBar!!.setTitle(R.string.app_name)
}
override fun onDrawerClosed(drawerView: View) {
mDrawerToggle!!.onDrawerClosed(drawerView)
if (mItemToOpenWhenDrawerCloses >= 0) {
val extras = ActivityOptions.makeCustomAnimation(
this@BaseDrawerActivity, R.anim.fade_in, R.anim.fade_out).toBundle()
var activityClass: Class<*>? = null
when (mItemToOpenWhenDrawerCloses) {
R.id.sub_setting -> activityClass = SubSettingActivity::class.java
R.id.settings -> activityClass = SettingsActivity::class.java
R.id.logcat -> {
startActivity(Intent(this@BaseDrawerActivity, LogcatActivity::class.java))
return
}
R.id.donate -> {
// startActivity<InappBuyActivity>()
return
}
}
if (activityClass != null) {
startActivity(Intent(this@BaseDrawerActivity, activityClass), extras)
finish()
}
}
}
override fun onDrawerStateChanged(newState: Int) {
mDrawerToggle!!.onDrawerStateChanged(newState)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Activity onCreate")
}
override fun onStart() {
super.onStart()
if (!mToolbarInitialized) {
throw IllegalStateException("You must run super.initializeToolbar at " + "the end of your onCreate method")
}
}
public override fun onResume() {
super.onResume()
// Whenever the fragment back stack changes, we may need to update the
// action bar toggle: only top level screens show the hamburger-like icon, inner
// screens - either Activities or fragments - show the "Up" icon instead.
fragmentManager.addOnBackStackChangedListener(backStackChangedListener)
}
public override fun onPause() {
super.onPause()
fragmentManager.removeOnBackStackChangedListener(backStackChangedListener)
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
mDrawerToggle!!.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mDrawerToggle!!.onConfigurationChanged(newConfig)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (mDrawerToggle != null && mDrawerToggle!!.onOptionsItemSelected(item)) {
return true
}
// If not handled by drawerToggle, home needs to be handled by returning to previous
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
// If the drawer is open, back will close it
if (mDrawerLayout != null && mDrawerLayout!!.isDrawerOpen(GravityCompat.START)) {
mDrawerLayout!!.closeDrawers()
return
}
// Otherwise, it may return to the previous fragment stack
val fragmentManager = fragmentManager
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack()
} else {
// Lastly, it will rely on the system behavior for back
super.onBackPressed()
}
}
private fun updateDrawerToggle() {
if (mDrawerToggle == null) {
return
}
val isRoot = fragmentManager.backStackEntryCount == 0
mDrawerToggle!!.isDrawerIndicatorEnabled = isRoot
supportActionBar!!.setDisplayShowHomeEnabled(!isRoot)
supportActionBar!!.setDisplayHomeAsUpEnabled(!isRoot)
supportActionBar!!.setHomeButtonEnabled(!isRoot)
if (isRoot) {
mDrawerToggle!!.syncState()
}
}
protected fun initializeToolbar() {
mToolbar = findViewById<View>(R.id.toolbar) as Toolbar
if (mToolbar == null) {
throw IllegalStateException("Layout is required to include a Toolbar with id " + "'toolbar'")
}
// mToolbar.inflateMenu(R.menu.main);
mDrawerLayout = findViewById<View>(R.id.drawer_layout) as DrawerLayout
if (mDrawerLayout != null) {
val navigationView = findViewById<View>(R.id.nav_view) as NavigationView
?: throw IllegalStateException("Layout requires a NavigationView " + "with id 'nav_view'")
// Create an ActionBarDrawerToggle that will handle opening/closing of the drawer:
mDrawerToggle = ActionBarDrawerToggle(this, mDrawerLayout,
mToolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
mDrawerLayout!!.addDrawerListener(drawerListener)
populateDrawerItems(navigationView)
setSupportActionBar(mToolbar)
updateDrawerToggle()
} else {
setSupportActionBar(mToolbar)
}
mToolbarInitialized = true
}
private fun populateDrawerItems(navigationView: NavigationView) {
navigationView.setNavigationItemSelectedListener { menuItem ->
menuItem.isChecked = true
mItemToOpenWhenDrawerCloses = menuItem.itemId
mDrawerLayout!!.closeDrawers()
true
}
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

@@ -1,21 +1,17 @@
package com.v2ray.ang.ui
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentStatePagerAdapter
class FragmentAdapter(fragmentActivity: FragmentActivity, private val mFragments: List<Fragment>) :
FragmentStateAdapter(fragmentActivity) {
class FragmentAdapter(fm: FragmentManager, private val mFragments: List<Fragment>, private val mTitles: List<String>) : FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
override fun createFragment(position: Int): Fragment {
return mFragments[position]
}
override fun getCount(): Int {
override fun getItemCount(): Int {
return mFragments.size
}
override fun getPageTitle(position: Int): CharSequence? {
return mTitles[position]
}
}
}

View File

@@ -7,10 +7,11 @@ import android.text.method.ScrollingMovementMethod
import android.view.Menu
import android.view.MenuItem
import android.view.View
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityLogcatBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_logcat.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -18,10 +19,13 @@ import java.io.IOException
import java.util.LinkedHashSet
class LogcatActivity : BaseActivity() {
private lateinit var binding: ActivityLogcatBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_logcat)
binding = ActivityLogcatBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_logcat)
@@ -32,7 +36,7 @@ class LogcatActivity : BaseActivity() {
private fun logcat(shouldFlushLog: Boolean) {
try {
pb_waiting.visibility = View.VISIBLE
binding.pbWaiting.visibility = View.VISIBLE
GlobalScope.launch(Dispatchers.Default) {
if (shouldFlushLog) {
@@ -48,17 +52,17 @@ class LogcatActivity : BaseActivity() {
lst.add("-v")
lst.add("time")
lst.add("-s")
lst.add("GoLog,tun2socks,com.v2ray.ang")
lst.add("GoLog,tun2socks,${ANG_PACKAGE},AndroidRuntime,System.err")
val process = Runtime.getRuntime().exec(lst.toTypedArray())
// val bufferedReader = BufferedReader(
// InputStreamReader(process.inputStream))
// val allText = bufferedReader.use(BufferedReader::readText)
val allText = process.inputStream.bufferedReader().use { it.readText() }
launch(Dispatchers.Main) {
tv_logcat.text = allText
tv_logcat.movementMethod = ScrollingMovementMethod()
pb_waiting.visibility = View.GONE
Handler(Looper.getMainLooper()).post { sv_logcat.fullScroll(View.FOCUS_DOWN) }
binding.tvLogcat.text = allText
binding.tvLogcat.movementMethod = ScrollingMovementMethod()
binding.pbWaiting.visibility = View.GONE
Handler(Looper.getMainLooper()).post { binding.svLogcat.fullScroll(View.FOCUS_DOWN) }
}
}
} catch (e: IOException) {
@@ -66,14 +70,14 @@ class LogcatActivity : BaseActivity() {
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_logcat, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.copy_all -> {
Utils.setClipboard(this, tv_logcat.text.toString())
Utils.setClipboard(this, binding.tvLogcat.text.toString())
toast(R.string.toast_success)
true
}

View File

@@ -1,182 +1,182 @@
package com.v2ray.ang.ui
import android.Manifest
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.net.Uri
import android.net.VpnService
import android.os.Bundle
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 com.google.android.material.navigation.NavigationView
import androidx.core.view.GravityCompat
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import android.text.TextUtils
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import com.tbruyelle.rxpermissions.RxPermissions
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.databinding.ActivityMainBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.SimpleItemTouchHelperCallback
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
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 me.drakeet.support.toast.ToastCompat
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.net.URL
import java.util.concurrent.TimeUnit
class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedListener {
companion object {
private const val REQUEST_CODE_VPN_PREPARE = 0
private const val REQUEST_SCAN = 1
private const val REQUEST_FILE_CHOOSER = 2
private const val REQUEST_SCAN_URL = 3
}
private lateinit var binding: ActivityMainBinding
private val adapter by lazy { MainRecyclerAdapter(this) }
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val requestVpnPermission = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
startV2Ray()
}
}
private var mItemTouchHelper: ItemTouchHelper? = null
private val mainViewModel: MainViewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_server)
setSupportActionBar(toolbar)
setSupportActionBar(binding.toolbar)
fab.setOnClickListener {
binding.fab.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
Utils.stopVService(this)
} else if (defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN") == "VPN") {
} else if (settingsStorage?.decodeString(AppConfig.PREF_MODE) ?: "VPN" == "VPN") {
val intent = VpnService.prepare(this)
if (intent == null) {
startV2Ray()
} else {
startActivityForResult(intent, REQUEST_CODE_VPN_PREPARE)
requestVpnPermission.launch(intent)
}
} else {
startV2Ray()
}
}
layout_test.setOnClickListener {
binding.layoutTest.setOnClickListener {
if (mainViewModel.isRunning.value == true) {
tv_test_state.text = getString(R.string.connection_test_testing)
binding.tvTestState.text = getString(R.string.connection_test_testing)
mainViewModel.testCurrentServerRealPing()
} else {
// tv_test_state.text = getString(R.string.connection_test_fail)
}
}
recycler_view.setHasFixedSize(true)
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
val callback = SimpleItemTouchHelperCallback(adapter)
mItemTouchHelper = ItemTouchHelper(callback)
mItemTouchHelper?.attachToRecyclerView(recycler_view)
mItemTouchHelper?.attachToRecyclerView(binding.recyclerView)
val toggle = ActionBarDrawerToggle(
this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
this, binding.drawerLayout, binding.toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
binding.drawerLayout.addDrawerListener(toggle)
toggle.syncState()
nav_view.setNavigationItemSelectedListener(this)
version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
binding.navView.setNavigationItemSelectedListener(this)
binding.version.text = "v${BuildConfig.VERSION_NAME} (${Libv2ray.checkVersionX()})"
setupViewModelObserver()
migrateLegacy()
}
private fun setupViewModelObserver() {
mainViewModel.updateListAction.observe(this, {
mainViewModel.updateListAction.observe(this) {
val index = it ?: return@observe
if (index >= 0) {
adapter.updateSelectedItem(index)
adapter.notifyItemChanged(index)
} else {
adapter.updateConfigList()
adapter.notifyDataSetChanged()
}
})
mainViewModel.updateTestResultAction.observe(this, { tv_test_state.text = it })
mainViewModel.isRunning.observe(this, {
}
mainViewModel.updateTestResultAction.observe(this) { binding.tvTestState.text = it }
mainViewModel.isRunning.observe(this) {
val isRunning = it ?: return@observe
adapter.changeable = !isRunning
adapter.isRunning = isRunning
if (isRunning) {
fab.setImageResource(R.drawable.ic_v)
tv_test_state.text = getString(R.string.connection_connected)
binding.fab.setImageResource(R.drawable.ic_v)
binding.tvTestState.text = getString(R.string.connection_connected)
binding.layoutTest.isFocusable = true
} else {
fab.setImageResource(R.drawable.ic_v_idle)
tv_test_state.text = getString(R.string.connection_not_connected)
binding.fab.setImageResource(R.drawable.ic_v_idle)
binding.tvTestState.text = getString(R.string.connection_not_connected)
binding.layoutTest.isFocusable = false
}
hideCircle()
})
}
mainViewModel.startListenBroadcast()
}
private fun migrateLegacy() {
GlobalScope.launch(Dispatchers.IO) {
val result = AngConfigManager.migrateLegacyConfig(this@MainActivity)
if (result != null) {
launch(Dispatchers.Main) {
if (result) {
toast(getString(R.string.migration_success))
mainViewModel.reloadServerList()
} else {
toast(getString(R.string.migration_fail))
}
}
}
}
}
fun startV2Ray() {
if (AngConfigManager.configs.index < 0) {
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
return
}
showCircle()
// toast(R.string.toast_services_start)
if (!Utils.startVService(this)) {
hideCircle()
}
V2RayServiceManager.startV2Ray(this)
hideCircle()
}
public override fun onResume() {
super.onResume()
adapter.updateConfigList()
mainViewModel.reloadServerList()
}
public override fun onPause() {
super.onPause()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE_VPN_PREPARE ->
if (resultCode == RESULT_OK) {
startV2Ray()
}
REQUEST_SCAN ->
if (resultCode == RESULT_OK) {
importBatchConfig(data?.getStringExtra("SCAN_RESULT"))
}
REQUEST_FILE_CHOOSER -> {
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
readContentFromUri(uri)
}
}
REQUEST_SCAN_URL ->
if (resultCode == RESULT_OK) {
importConfigCustomUrl(data?.getStringExtra("SCAN_RESULT"))
}
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
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)
importQRcode(true)
true
}
R.id.import_clipboard -> {
@@ -184,18 +184,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
R.id.import_manually_vmess -> {
startActivity(getOptionIntent().setClass(this, ServerActivity::class.java))
adapter.updateConfigList()
startActivity(Intent().putExtra("createConfigType", EConfigType.VMESS.value).
setClass(this, ServerActivity::class.java))
true
}
R.id.import_manually_ss -> {
startActivity(getOptionIntent().setClass(this, Server3Activity::class.java))
adapter.updateConfigList()
startActivity(Intent().putExtra("createConfigType", EConfigType.SHADOWSOCKS.value).
setClass(this, ServerActivity::class.java))
true
}
R.id.import_manually_socks -> {
startActivity(getOptionIntent().setClass(this, Server4Activity::class.java))
adapter.updateConfigList()
startActivity(Intent().putExtra("createConfigType", EConfigType.SOCKS.value).
setClass(this, ServerActivity::class.java))
true
}
R.id.import_config_custom_clipboard -> {
@@ -211,7 +211,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
true
}
R.id.import_config_custom_url_scan -> {
importQRcode(REQUEST_SCAN_URL)
importQRcode(false)
true
}
@@ -226,8 +226,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
R.id.export_all -> {
if (AngConfigManager.shareAll2Clipboard() == 0) {
//remove toast, otherwise it will block previous warning message
if (AngConfigManager.shareNonCustomConfigsToClipboard(this, mainViewModel.serverList) == 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
@@ -254,7 +254,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
/**
* import config from qrcode
*/
fun importQRcode(requestCode: Int): Boolean {
fun importQRcode(forConfig: Boolean): Boolean {
// try {
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
// .addCategory(Intent.CATEGORY_DEFAULT)
@@ -264,7 +264,10 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
if (forConfig)
scanQRCodeForConfig.launch(Intent(this, ScannerActivity::class.java))
else
scanQRCodeForUrlToCustomConfig.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
@@ -272,6 +275,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
return true
}
private val scanQRCodeForConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"))
}
}
private val scanQRCodeForUrlToCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
importConfigCustomUrl(it.data?.getStringExtra("SCAN_RESULT"))
}
}
/**
* import config from clipboard
*/
@@ -288,10 +303,13 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
fun importBatchConfig(server: String?, subid: String = "") {
val count = AngConfigManager.importBatchConfig(server, subid)
var count = AngConfigManager.importBatchConfig(server, subid)
if (count <= 0) {
count = AngConfigManager.importBatchConfig(Utils.decode(server!!), subid)
}
if (count > 0) {
toast(R.string.toast_success)
adapter.updateConfigList()
mainViewModel.reloadServerList()
} else {
toast(R.string.toast_failure)
}
@@ -352,7 +370,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
GlobalScope.launch(Dispatchers.IO) {
val configText = try {
URL(url).readText()
Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) {
e.printStackTrace()
""
@@ -375,29 +393,30 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
: Boolean {
try {
toast(R.string.title_sub_update)
val subItem = AngConfigManager.configs.subItem
for (k in 0 until subItem.count()) {
if (TextUtils.isEmpty(subItem[k].id)
|| TextUtils.isEmpty(subItem[k].remarks)
|| TextUtils.isEmpty(subItem[k].url)
MmkvManager.decodeSubscriptions().forEach {
if (TextUtils.isEmpty(it.first)
|| TextUtils.isEmpty(it.second.remarks)
|| TextUtils.isEmpty(it.second.url)
) {
continue
return@forEach
}
val id = subItem[k].id
val url = subItem[k].url
val url = it.second.url
if (!Utils.isValidUrl(url)) {
continue
return@forEach
}
Log.d("Main", url)
Log.d(ANG_PACKAGE, url)
GlobalScope.launch(Dispatchers.IO) {
val configText = try {
URL(url).readText()
Utils.getUrlContentWithCustomUserAgent(url)
} catch (e: Exception) {
e.printStackTrace()
""
launch(Dispatchers.Main) {
toast("\"" + it.second.remarks + "\" " + getString(R.string.toast_failure))
}
return@launch
}
launch(Dispatchers.Main) {
importBatchConfig(Utils.decode(configText), id)
importBatchConfig(Utils.decode(configText), it.first)
}
}
}
@@ -417,14 +436,19 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
intent.addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.title_file_chooser)),
REQUEST_FILE_CHOOSER)
chooseFileForCustomConfig.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
}
}
private val chooseFileForCustomConfig = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
readContentFromUri(uri)
}
}
/**
* read content from uri
*/
@@ -434,9 +458,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
.subscribe {
if (it) {
try {
contentResolver.openInputStream(uri).use {
val configText = it?.bufferedReader()?.readText()
importCustomizeConfig(configText)
contentResolver.openInputStream(uri).use { input ->
importCustomizeConfig(input?.bufferedReader()?.readText())
}
} catch (e: Exception) {
e.printStackTrace()
@@ -450,19 +473,18 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
* import customize config
*/
fun importCustomizeConfig(server: String?) {
if (server == null) {
return
}
if (!V2rayConfigUtil.isValidConfig(server)) {
toast(R.string.toast_config_file_invalid)
return
}
val resId = AngConfigManager.importCustomizeConfig(server)
if (resId > 0) {
toast(resId)
} else {
try {
if (server == null || TextUtils.isEmpty(server)) {
toast(R.string.toast_none_data)
return
}
mainViewModel.appendCustomConfigServer(server)
toast(R.string.toast_success)
adapter.updateConfigList()
adapter.notifyItemInserted(mainViewModel.serverList.lastIndex)
} catch (e: Exception) {
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
e.printStackTrace()
return
}
}
@@ -484,7 +506,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
fun showCircle() {
fabProgressCircle?.show()
binding.fabProgressCircle.show()
}
fun hideCircle() {
@@ -492,8 +514,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
Observable.timer(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (fabProgressCircle.isShown) {
fabProgressCircle.hide()
if (binding.fabProgressCircle.isShown) {
binding.fabProgressCircle.hide()
}
}
} catch (e: Exception) {
@@ -501,8 +523,8 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
}
override fun onBackPressed() {
if (drawer_layout.isDrawerOpen(GravityCompat.START)) {
drawer_layout.closeDrawer(GravityCompat.START)
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START)
} else {
super.onBackPressed()
}
@@ -532,7 +554,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
startActivity(Intent(this, LogcatActivity::class.java))
}
}
drawer_layout.closeDrawer(GravityCompat.START)
binding.drawerLayout.closeDrawer(GravityCompat.START)
return true
}
}

View File

@@ -2,24 +2,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 androidx.core.content.ContextCompat
import androidx.appcompat.app.AlertDialog
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.databinding.ItemQrcodeBinding
import com.v2ray.ang.databinding.ItemRecyclerFooterBinding
import com.v2ray.ang.databinding.ItemRecyclerMainBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.helper.ItemTouchHelperAdapter
import com.v2ray.ang.helper.ItemTouchHelperViewHolder
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
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 rx.Observable
import rx.android.schedulers.AndroidSchedulers
import java.util.concurrent.TimeUnit
@@ -32,81 +36,74 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
private var mActivity: MainActivity = activity
private lateinit var configs: AngConfig
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val share_method: Array<out String> by lazy {
mActivity.resources.getStringArray(R.array.share_method)
}
var isRunning = false
var changeable: Boolean = true
set(value) {
if (field == value)
return
field = value
notifyDataSetChanged()
}
init {
updateConfigList()
}
override fun getItemCount() = configs.vmess.count() + 1
override fun getItemCount() = mActivity.mainViewModel.serverList.size + 1
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) {
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
val port = configs.vmess[position].port
val test_result = configs.vmess[position].testResult
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
val config = mActivity.mainViewModel.serversCache.getOrElse(guid) { MmkvManager.decodeServerConfig(guid) } ?: return
val outbound = config.getProxyOutbound()
val aff = MmkvManager.decodeServerAffiliationInfo(guid)
holder.name.text = remarks
holder.radio.isChecked = (position == configs.index)
holder.itemMainBinding.tvName.text = config.remarks
holder.itemMainBinding.btnRadio.isChecked = guid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.test_result.text = test_result
if (TextUtils.isEmpty(subid)) {
holder.subid.text = ""
holder.itemMainBinding.tvTestResult.text = aff?.getTestDelayString() ?: ""
if (aff?.testDelayMillis?:0L < 0L) {
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, android.R.color.holo_red_dark))
} else {
holder.subid.text = "S"
holder.itemMainBinding.tvTestResult.setTextColor(ContextCompat.getColor(mActivity, R.color.colorPing))
}
holder.itemMainBinding.tvSubscription.text = ""
val json = subStorage?.decodeString(config.subscriptionId)
if (!json.isNullOrBlank()) {
val sub = Gson().fromJson(json, SubscriptionItem::class.java)
holder.itemMainBinding.tvSubscription.text = sub.remarks
}
var shareOptions = share_method.asList()
if (configType == EConfigType.CUSTOM) {
holder.type.text = mActivity.getString(R.string.server_customize_config)
val serverOutbound = V2rayConfigUtil.getCustomConfigServerOutbound(mActivity.applicationContext, configs.vmess[position].guid)
if (serverOutbound == null) {
holder.statistics.text = ""
} else {
holder.statistics.text = "${serverOutbound.getServerAddress()} : ${serverOutbound.getServerPort()}"
when (config.configType) {
EConfigType.CUSTOM -> {
holder.itemMainBinding.tvType.text = mActivity.getString(R.string.server_customize_config)
shareOptions = shareOptions.takeLast(1)
}
EConfigType.VLESS -> {
holder.itemMainBinding.tvType.text = config.configType.name
}
else -> {
holder.itemMainBinding.tvType.text = config.configType.name.lowercase()
}
shareOptions = shareOptions.takeLast(1)
} else {
holder.type.text = configType?.name?.toLowerCase()
holder.statistics.text = "$address : $port"
}
holder.itemMainBinding.tvStatistics.text = "${outbound?.getServerAddress()} : ${outbound?.getServerPort()}"
holder.layout_share.setOnClickListener {
holder.itemMainBinding.layoutShare.setOnClickListener {
AlertDialog.Builder(mActivity).setItems(shareOptions.toTypedArray()) { _, i ->
try {
when (i) {
0 -> {
if (configType == EConfigType.CUSTOM) {
shareFullContent(position)
if (config.configType == EConfigType.CUSTOM) {
shareFullContent(guid)
} else {
val iv = mActivity.layoutInflater.inflate(R.layout.item_qrcode, null)
iv.iv_qcode.setImageBitmap(AngConfigManager.share2QRCode(position))
AlertDialog.Builder(mActivity).setView(iv).show()
val ivBinding = ItemQrcodeBinding.inflate(LayoutInflater.from(mActivity))
ivBinding.ivQcode.setImageBitmap(AngConfigManager.share2QRCode(guid))
AlertDialog.Builder(mActivity).setView(ivBinding.root).show()
}
}
1 -> {
if (AngConfigManager.share2Clipboard(position) == 0) {
if (AngConfigManager.share2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
}
}
2 -> shareFullContent(position)
2 -> shareFullContent(guid)
else -> mActivity.toast("else")
}
} catch (e: Exception) {
@@ -115,62 +112,56 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}.show()
}
holder.layout_edit.setOnClickListener {
val intent = Intent().putExtra("position", position)
.putExtra("isRunning", !changeable)
if (configType == EConfigType.VMESS) {
holder.itemMainBinding.layoutEdit.setOnClickListener {
val intent = Intent().putExtra("guid", guid)
.putExtra("isRunning", isRunning)
if (config.configType == EConfigType.CUSTOM) {
mActivity.startActivity(intent.setClass(mActivity, ServerCustomConfigActivity::class.java))
} else {
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 {
if (configs.index != position) {
if (AngConfigManager.removeServer(position) == 0) {
notifyItemRemoved(position)
updateSelectedItem(position)
}
holder.itemMainBinding.layoutRemove.setOnClickListener {
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
notifyItemRangeChanged(position, mActivity.mainViewModel.serverList.size)
}
}
holder.infoContainer.setOnClickListener {
if (changeable) {
AngConfigManager.setActiveServer(position)
} else {
mActivity.showCircle()
Utils.stopVService(mActivity)
AngConfigManager.setActiveServer(position)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
mActivity.showCircle()
if (!Utils.startVService(mActivity)) {
holder.itemMainBinding.infoContainer.setOnClickListener {
val selected = mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
if (guid != selected) {
mainStorage?.encode(MmkvManager.KEY_SELECTED_SERVER, guid)
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(selected))
notifyItemChanged(mActivity.mainViewModel.serverList.indexOf(guid))
if (isRunning) {
mActivity.showCircle()
Utils.stopVService(mActivity)
Observable.timer(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
V2RayServiceManager.startV2Ray(mActivity)
mActivity.hideCircle()
}
}
}
}
notifyDataSetChanged()
}
}
if (holder is FooterViewHolder) {
//if (activity?.defaultDPreference?.getPrefBoolean(AppConfig.PREF_INAPP_BUY_IS_PREMIUM, false)) {
if (true) {
holder.layout_edit.visibility = View.INVISIBLE
holder.itemFooterBinding.layoutEdit.visibility = View.INVISIBLE
} else {
holder.layout_edit.setOnClickListener {
Utils.openUri(mActivity, AppConfig.promotionUrl)
holder.itemFooterBinding.layoutEdit.setOnClickListener {
Utils.openUri(mActivity, "${Utils.decode(AppConfig.promotionUrl)}?t=${System.currentTimeMillis()}")
}
}
}
}
private fun shareFullContent(position: Int) {
if (AngConfigManager.shareFullContent2Clipboard(position) == 0) {
private fun shareFullContent(guid: String) {
if (AngConfigManager.shareFullContent2Clipboard(mActivity, guid) == 0) {
mActivity.toast(R.string.toast_success)
} else {
mActivity.toast(R.string.toast_failure)
@@ -178,96 +169,63 @@ class MainRecyclerAdapter(val activity: MainActivity) : RecyclerView.Adapter<Mai
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
when (viewType) {
return when (viewType) {
VIEW_TYPE_ITEM ->
return MainViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_main, parent, false))
MainViewHolder(ItemRecyclerMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
else ->
return FooterViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_footer, parent, false))
FooterViewHolder(ItemRecyclerFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
}
fun updateConfigList() {
configs = AngConfigManager.configs
notifyDataSetChanged()
}
// fun updateSelectedItem() {
// updateSelectedItem(configs.index)
// }
fun updateSelectedItem(pos: Int) {
//notifyItemChanged(pos)
notifyItemRangeChanged(pos, itemCount - pos)
}
override fun getItemViewType(position: Int): Int {
if (position == configs.vmess.count()) {
return VIEW_TYPE_FOOTER
return if (position == mActivity.mainViewModel.serverList.size) {
VIEW_TYPE_FOOTER
} else {
return VIEW_TYPE_ITEM
VIEW_TYPE_ITEM
}
}
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
val subid = itemView.tv_subid
val radio = itemView.btn_radio!!
val name = itemView.tv_name!!
val test_result = itemView.tv_test_result!!
val type = itemView.tv_type!!
val statistics = itemView.tv_statistics!!
val infoContainer = itemView.info_container!!
val layout_edit = itemView.layout_edit!!
val layout_share = itemView.layout_share
val layout_remove = itemView.layout_remove!!
override fun onItemSelected() {
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onItemSelected() {
itemView.setBackgroundColor(Color.LTGRAY)
}
override fun onItemClear() {
fun onItemClear() {
itemView.setBackgroundColor(0)
}
}
class FooterViewHolder(itemView: View) : BaseViewHolder(itemView), ItemTouchHelperViewHolder {
val layout_edit = itemView.layout_edit!!
class MainViewHolder(val itemMainBinding: ItemRecyclerMainBinding) :
BaseViewHolder(itemMainBinding.root), ItemTouchHelperViewHolder
override fun onItemSelected() {
itemView.setBackgroundColor(Color.LTGRAY)
}
override fun onItemClear() {
itemView.setBackgroundColor(0)
}
}
class FooterViewHolder(val itemFooterBinding: ItemRecyclerFooterBinding) :
BaseViewHolder(itemFooterBinding.root), ItemTouchHelperViewHolder
override fun onItemDismiss(position: Int) {
if (configs.index != position) {
val guid = mActivity.mainViewModel.serverList.getOrNull(position) ?: return
if (guid != mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)) {
// mActivity.alert(R.string.del_config_comfirm) {
// positiveButton(android.R.string.ok) {
if (AngConfigManager.removeServer(position) == 0) {
notifyItemRemoved(position)
}
mActivity.mainViewModel.removeServer(guid)
notifyItemRemoved(position)
// }
// show()
// }
}
updateSelectedItem(position)
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
AngConfigManager.swapServer(fromPosition, toPosition)
mActivity.mainViewModel.swapServer(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
//notifyDataSetChanged()
updateSelectedItem(if (fromPosition < toPosition) fromPosition else toPosition)
// position is changed, since position is used by click callbacks, need to update range
if (toPosition > fromPosition)
notifyItemRangeChanged(fromPosition, toPosition - fromPosition + 1)
else
notifyItemRangeChanged(toPosition, fromPosition - toPosition + 1)
return true
}
override fun onItemMoveCompleted() {
AngConfigManager.storeConfigFile()
// do nothing
}
}

View File

@@ -4,9 +4,10 @@ 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 androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.text.TextUtils
import android.util.Log
import android.view.Menu
@@ -15,9 +16,7 @@ import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import com.v2ray.ang.R
import com.v2ray.ang.extension.defaultDPreference
import com.v2ray.ang.util.AppManagerUtil
import kotlinx.android.synthetic.main.activity_bypass_list.*
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.text.Collator
@@ -25,6 +24,8 @@ import java.util.*
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.databinding.ActivityBypassListBinding
import com.v2ray.ang.dto.AppInfo
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
@@ -35,24 +36,24 @@ import kotlinx.coroutines.launch
import java.net.URL
class PerAppProxyActivity : BaseActivity() {
companion object {
const val PREF_PER_APP_PROXY_SET = "pref_per_app_proxy_set"
const val PREF_BYPASS_APPS = "pref_bypass_apps"
}
private lateinit var binding: ActivityBypassListBinding
private var adapter: PerAppProxyAdapter? = null
private var appsAll: List<AppInfo>? = null
private val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bypass_list)
binding = ActivityBypassListBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val dividerItemDecoration = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
recycler_view.addItemDecoration(dividerItemDecoration)
binding.recyclerView.addItemDecoration(dividerItemDecoration)
val blacklist = defaultDPreference.getPrefStringSet(PREF_PER_APP_PROXY_SET, null)
val blacklist = defaultSharedPreferences.getStringSet(AppConfig.PREF_PER_APP_PROXY_SET, null)
AppManagerUtil.rxLoadNetworkAppList(this)
.subscribeOn(Schedulers.io())
@@ -65,8 +66,8 @@ class PerAppProxyActivity : BaseActivity() {
one.isSelected = 0
}
}
val comparator = object : Comparator<AppInfo> {
override fun compare(p1: AppInfo, p2: AppInfo): Int = when {
val comparator = Comparator<AppInfo> { p1, p2 ->
when {
p1.isSelected > p2.isSelected -> -1
p1.isSelected == p2.isSelected -> 0
else -> 1
@@ -92,20 +93,20 @@ class PerAppProxyActivity : BaseActivity() {
.subscribe {
appsAll = it
adapter = PerAppProxyAdapter(this, it, blacklist)
recycler_view.adapter = adapter
pb_waiting.visibility = View.GONE
binding.recyclerView.adapter = adapter
binding.pbWaiting.visibility = View.GONE
}
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
var dst = 0
val threshold = resources.getDimensionPixelSize(R.dimen.bypass_list_header_height) * 3
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
dst += dy
if (dst > threshold) {
header_view.hide()
binding.headerView.hide()
dst = 0
} else if (dst < -20) {
header_view.show()
binding.headerView.show()
dst = 0
}
}
@@ -141,23 +142,23 @@ class PerAppProxyActivity : BaseActivity() {
}
})
switch_per_app_proxy.setOnCheckedChangeListener { buttonView, isChecked ->
defaultDPreference.setPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, isChecked)
binding.switchPerAppProxy.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_PER_APP_PROXY, isChecked).apply()
}
switch_per_app_proxy.isChecked = defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PER_APP_PROXY, false)
binding.switchPerAppProxy.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
switch_bypass_apps.setOnCheckedChangeListener { buttonView, isChecked ->
defaultDPreference.setPrefBoolean(PREF_BYPASS_APPS, isChecked)
binding.switchBypassApps.setOnCheckedChangeListener { _, isChecked ->
defaultSharedPreferences.edit().putBoolean(AppConfig.PREF_BYPASS_APPS, isChecked).apply()
}
switch_bypass_apps.isChecked = defaultDPreference.getPrefBoolean(PREF_BYPASS_APPS, false)
binding.switchBypassApps.isChecked = defaultSharedPreferences.getBoolean(AppConfig.PREF_BYPASS_APPS, false)
et_search.setOnEditorActionListener { v, actionId, event ->
binding.etSearch.setOnEditorActionListener { v, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
//hide
var imm: InputMethodManager = v.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
val key = v.text.toString().toUpperCase()
val key = v.text.toString().uppercase()
val apps = ArrayList<AppInfo>()
if (TextUtils.isEmpty(key)) {
appsAll?.forEach {
@@ -165,13 +166,13 @@ class PerAppProxyActivity : BaseActivity() {
}
} else {
appsAll?.forEach {
if (it.appName.toUpperCase().indexOf(key) >= 0) {
if (it.appName.uppercase().indexOf(key) >= 0) {
apps.add(it)
}
}
}
adapter = PerAppProxyAdapter(this, apps, adapter?.blacklist)
recycler_view.adapter = adapter
binding.recyclerView.adapter = adapter
adapter?.notifyDataSetChanged()
true
} else {
@@ -183,11 +184,11 @@ class PerAppProxyActivity : BaseActivity() {
override fun onPause() {
super.onPause()
adapter?.let {
defaultDPreference.setPrefStringSet(PREF_PER_APP_PROXY_SET, it.blacklist)
defaultSharedPreferences.edit().putStringSet(AppConfig.PREF_PER_APP_PROXY_SET, it.blacklist).apply()
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_bypass_list, menu)
return super.onCreateOptionsMenu(menu)
}
@@ -229,7 +230,7 @@ class PerAppProxyActivity : BaseActivity() {
""
}
launch(Dispatchers.Main) {
Log.d("selectProxyApp", content)
Log.d(ANG_PACKAGE, content)
selectProxyApp(content)
toast(R.string.toast_success)
}
@@ -249,11 +250,11 @@ class PerAppProxyActivity : BaseActivity() {
adapter?.blacklist!!.clear()
if (switch_bypass_apps.isChecked) {
if (binding.switchBypassApps.isChecked) {
adapter?.let {
it.apps.forEach block@{
val packageName = it.packageName
Log.d("selectProxyApp2", packageName)
Log.d(ANG_PACKAGE, packageName)
if (proxyApps.indexOf(packageName) < 0) {
adapter?.blacklist!!.add(packageName)
println(packageName)
@@ -266,7 +267,7 @@ class PerAppProxyActivity : BaseActivity() {
adapter?.let {
it.apps.forEach block@{
val packageName = it.packageName
Log.d("selectProxyApp3", packageName)
Log.d(ANG_PACKAGE, packageName)
if (proxyApps.indexOf(packageName) >= 0) {
adapter?.blacklist!!.add(packageName)
println(packageName)

View File

@@ -1,13 +1,13 @@
package com.v2ray.ang.ui
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ItemRecyclerBypassListBinding
import com.v2ray.ang.dto.AppInfo
import kotlinx.android.synthetic.main.item_recycler_bypass_list.view.*
import java.util.*
class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, blacklist: MutableSet<String>?) :
@@ -18,8 +18,7 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
private const val VIEW_TYPE_ITEM = 1
}
private var mActivity: BaseActivity = activity
val blacklist = if (blacklist == null) HashSet<String>() else HashSet<String>(blacklist)
val blacklist = if (blacklist == null) HashSet() else HashSet(blacklist)
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is AppViewHolder) {
@@ -43,8 +42,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(LayoutInflater.from(ctx)
.inflate(R.layout.item_recycler_bypass_list, parent, false))
else -> AppViewHolder(ItemRecyclerBypassListBinding.inflate(LayoutInflater.from(ctx), parent, false))
}
}
@@ -53,30 +51,25 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
inner class AppViewHolder(itemView: View) : BaseViewHolder(itemView),
inner class AppViewHolder(private val itemBypassBinding: ItemRecyclerBypassListBinding) : BaseViewHolder(itemBypassBinding.root),
View.OnClickListener {
private val inBlacklist: Boolean get() = blacklist.contains(appInfo.packageName)
private lateinit var appInfo: AppInfo
val icon = itemView.icon!!
val name = itemView.name!!
val package_name = itemView.package_name!!
val checkBox = itemView.check_box!!
fun bind(appInfo: AppInfo) {
this.appInfo = appInfo
icon.setImageDrawable(appInfo.appIcon)
itemBypassBinding.icon.setImageDrawable(appInfo.appIcon)
// name.text = appInfo.appName
checkBox.isChecked = inBlacklist
package_name.text = appInfo.packageName
itemBypassBinding.checkBox.isChecked = inBlacklist
itemBypassBinding.packageName.text = appInfo.packageName
if (appInfo.isSystemApp) {
name.text = String.format("** %1s", appInfo.appName)
name.setTextColor(Color.RED)
itemBypassBinding.name.text = String.format("** %1s", appInfo.appName)
itemBypassBinding.name.setTextColor(Color.RED)
} else {
name.text = appInfo.appName
name.setTextColor(Color.DKGRAY)
itemBypassBinding.name.text = appInfo.appName
itemBypassBinding.name.setTextColor(Color.DKGRAY)
}
itemView.setOnClickListener(this)
@@ -85,10 +78,10 @@ class PerAppProxyAdapter(val activity: BaseActivity, val apps: List<AppInfo>, bl
override fun onClick(v: View?) {
if (inBlacklist) {
blacklist.remove(appInfo.packageName)
checkBox.isChecked = false
itemBypassBinding.checkBox.isChecked = false
} else {
blacklist.add(appInfo.packageName)
checkBox.isChecked = true
itemBypassBinding.checkBox.isChecked = true
}
}
}

View File

@@ -3,19 +3,23 @@ package com.v2ray.ang.ui
import android.graphics.Color
import android.os.Bundle
import com.v2ray.ang.R
import android.support.v4.app.Fragment
import androidx.fragment.app.Fragment
import com.google.android.material.tabs.TabLayoutMediator
import com.v2ray.ang.AppConfig
import kotlinx.android.synthetic.main.activity_routing_settings.*
import com.v2ray.ang.databinding.ActivityRoutingSettingsBinding
class RoutingSettingsActivity : BaseActivity() {
private lateinit var binding: ActivityRoutingSettingsBinding
private val titles: Array<out String> by lazy {
resources.getStringArray(R.array.routing_tag)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_routing_settings)
binding = ActivityRoutingSettingsBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.routing_settings_title)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
@@ -25,9 +29,11 @@ class RoutingSettingsActivity : BaseActivity() {
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_DIRECT))
fragments.add(RoutingSettingsFragment().newInstance(AppConfig.PREF_V2RAY_ROUTING_BLOCKED))
val adapter = FragmentAdapter(supportFragmentManager, fragments, titles.toList())
viewpager?.adapter = adapter
tablayout.setTabTextColors(Color.BLACK, Color.RED)
tablayout.setupWithViewPager(viewpager)
val adapter = FragmentAdapter(this, fragments)
binding.viewpager.adapter = adapter
binding.tablayout.setTabTextColors(Color.BLACK, Color.RED)
TabLayoutMediator(binding.tablayout, binding.viewpager) { tab, position ->
tab.text = titles[position]
}.attach()
}
}

View File

@@ -4,32 +4,37 @@ 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 androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
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 android.view.MenuInflater
import androidx.activity.result.contract.ActivityResultContracts
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.AppConfig
import com.v2ray.ang.R
import com.v2ray.ang.databinding.FragmentRoutingSettingsBinding
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.util.Utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.net.URL
class RoutingSettingsFragment : Fragment() {
private lateinit var binding: FragmentRoutingSettingsBinding
companion object {
private const val routing_arg = "routing_arg"
private const val REQUEST_SCAN_REPLACE = 11
private const val REQUEST_SCAN_APPEND = 12
}
val defaultSharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(requireContext()) }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_routing_settings, container, false)
binding = FragmentRoutingSettingsBinding.inflate(layoutInflater)
return binding.root// inflater.inflate(R.layout.fragment_routing_settings, container, false)
}
fun newInstance(arg: String): Fragment {
@@ -40,11 +45,11 @@ class RoutingSettingsFragment : Fragment() {
return fragment
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val content = activity?.defaultDPreference?.getPrefString(arguments!!.getString(routing_arg), "")
et_routing_content.text = Utils.getEditable(content!!)
val content = defaultSharedPreferences.getString(requireArguments().getString(routing_arg), "")
binding.etRoutingContent.text = Utils.getEditable(content!!)
setHasOptionsMenu(true)
}
@@ -56,21 +61,21 @@ class RoutingSettingsFragment : Fragment() {
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.save_routing -> {
val content = et_routing_content.text.toString()
activity?.defaultDPreference?.setPrefString(arguments!!.getString(routing_arg), content)
val content = binding.etRoutingContent.text.toString()
defaultSharedPreferences.edit().putString(requireArguments().getString(routing_arg), content).apply()
activity?.toast(R.string.toast_success)
true
}
R.id.del_routing -> {
et_routing_content.text = null
binding.etRoutingContent.text = null
true
}
R.id.scan_replace -> {
scanQRcode(REQUEST_SCAN_REPLACE)
scanQRcode(true)
true
}
R.id.scan_append -> {
scanQRcode(REQUEST_SCAN_APPEND)
scanQRcode(false)
true
}
R.id.default_rules -> {
@@ -80,17 +85,26 @@ class RoutingSettingsFragment : Fragment() {
else -> super.onOptionsItemSelected(item)
}
fun scanQRcode(requestCode: Int): Boolean {
private fun saveRouting() {
val content = binding.etRoutingContent.text.toString()
defaultSharedPreferences.edit().putString(requireArguments().getString(routing_arg), content).apply()
activity?.toast(R.string.toast_success)
}
fun scanQRcode(forReplace: Boolean): Boolean {
// try {
// startActivityForResult(Intent("com.google.zxing.client.android.SCAN")
// .addCategory(Intent.CATEGORY_DEFAULT)
// .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), requestCode)
// } catch (e: Exception) {
RxPermissions(activity!!)
RxPermissions(requireActivity())
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult(Intent(activity, ScannerActivity::class.java), requestCode)
if (forReplace)
scanQRCodeForReplace.launch(Intent(activity, ScannerActivity::class.java))
else
scanQRCodeForAppend.launch(Intent(activity, ScannerActivity::class.java))
else
activity?.toast(R.string.toast_permission_denied)
}
@@ -98,9 +112,23 @@ class RoutingSettingsFragment : Fragment() {
return true
}
private val scanQRCodeForReplace = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val content = it.data?.getStringExtra("SCAN_RESULT")
binding.etRoutingContent.text = Utils.getEditable(content!!)
}
}
private val scanQRCodeForAppend = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val content = it.data?.getStringExtra("SCAN_RESULT")
binding.etRoutingContent.text = Utils.getEditable("${binding.etRoutingContent.text},$content")
}
}
fun setDefaultRules(): Boolean {
var url = AppConfig.v2rayCustomRoutingListUrl
when (arguments!!.getString(routing_arg)) {
when (requireArguments().getString(routing_arg)) {
AppConfig.PREF_V2RAY_ROUTING_AGENT -> {
url += AppConfig.TAG_AGENT
}
@@ -121,28 +149,16 @@ class RoutingSettingsFragment : Fragment() {
""
}
launch(Dispatchers.Main) {
et_routing_content.text = Utils.getEditable(content)
activity?.toast(R.string.toast_success)
val routingList = if (TextUtils.isEmpty(content)) {
Utils.readTextFromAssets(activity?.v2RayApplication!!, "custom_routing_$tag")
} else {
content
}
binding.etRoutingContent.text = Utils.getEditable(routingList)
saveRouting()
//toast(R.string.toast_success)
}
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_SCAN_REPLACE ->
if (resultCode == RESULT_OK) {
val content = data?.getStringExtra("SCAN_RESULT")
et_routing_content.text = Utils.getEditable(content!!)
}
REQUEST_SCAN_APPEND ->
if (resultCode == RESULT_OK) {
val content = data?.getStringExtra("SCAN_RESULT")
et_routing_content.text = Utils.getEditable("${et_routing_content.text},$content")
}
}
}
}

View File

@@ -6,25 +6,23 @@ import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import com.v2ray.ang.extension.toast
class ScScannerActivity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_none)
importQRcode(REQUEST_SCAN)
importQRcode()
}
fun importQRcode(requestCode: Int): Boolean {
fun importQRcode(): Boolean {
RxPermissions(this)
.request(Manifest.permission.CAMERA)
.subscribe {
if (it)
startActivityForResult(Intent(this, ScannerActivity::class.java), requestCode)
scanQRCode.launch(Intent(this, ScannerActivity::class.java))
else
toast(R.string.toast_permission_denied)
}
@@ -32,21 +30,16 @@ class ScScannerActivity : BaseActivity() {
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_SCAN ->
if (resultCode == RESULT_OK) {
val count = AngConfigManager.importBatchConfig(data?.getStringExtra("SCAN_RESULT"), "")
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
startActivity(Intent(this, MainActivity::class.java))
}
private val scanQRCode = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
val count = AngConfigManager.importBatchConfig(it.data?.getStringExtra("SCAN_RESULT"), "")
if (count > 0) {
toast(R.string.toast_success)
} else {
toast(R.string.toast_failure)
}
startActivity(Intent(this, MainActivity::class.java))
}
finish()
}
}

View File

@@ -9,6 +9,7 @@ import android.content.Intent
import android.graphics.BitmapFactory
import android.view.Menu
import android.view.MenuItem
import androidx.activity.result.contract.ActivityResultContracts
import com.google.zxing.BarcodeFormat
import com.tbruyelle.rxpermissions.RxPermissions
import com.v2ray.ang.R
@@ -16,15 +17,11 @@ import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.QRCodeDecoder
class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
companion object {
private const val REQUEST_FILE_CHOOSER = 2
}
private var mScannerView: ZXingScannerView? = null
public override fun onCreate(state: Bundle?) {
super.onCreate(state)
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mScannerView = ZXingScannerView(this) // Programmatically initialize the scanner view
mScannerView?.setAutoFocus(true)
@@ -59,14 +56,14 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
// mScannerView!!.resumeCameraPreview(this)
}
fun finished(text: String) {
private fun finished(text: String) {
val intent = Intent()
intent.putExtra("SCAN_RESULT", text)
setResult(Activity.RESULT_OK, intent)
finish()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_scanner, menu)
return true
}
@@ -97,30 +94,22 @@ class ScannerActivity : BaseActivity(), ZXingScannerView.ResultHandler {
//intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
try {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.title_file_chooser)),
REQUEST_FILE_CHOOSER)
chooseFile.launch(Intent.createChooser(intent, getString(R.string.title_file_chooser)))
} catch (ex: android.content.ActivityNotFoundException) {
toast(R.string.toast_require_file_manager)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_FILE_CHOOSER -> {
val uri = data?.data
if (resultCode == RESULT_OK && uri != null) {
try {
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text)
} catch (e: Exception) {
e.printStackTrace()
toast(e.message.toString())
}
}
private val chooseFile = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val uri = it.data?.data
if (it.resultCode == RESULT_OK && uri != null) {
try {
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri))
val text = QRCodeDecoder.syncDecodeQRCode(bitmap)
finished(text)
} catch (e: Exception) {
e.printStackTrace()
toast(e.message.toString())
}
}
}

View File

@@ -1,160 +0,0 @@
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
import android.view.MenuItem
import com.google.gson.Gson
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 java.lang.Exception
class Server2Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server2)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
tv_content.text = Editable.Factory.getInstance().newEditable(defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + edit_guid, ""))
return true
}
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
return true
}
/**
* 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
}
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
}
if (saveSuccess) {
//update config
defaultDPreference.setPrefString(AppConfig.ANG_CONFIG + edit_guid, tv_content.text.toString())
finish()
return true
} else {
return false
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
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()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -1,174 +0,0 @@
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.*
class Server3Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
private val securitys: Array<out String> by lazy {
resources.getStringArray(R.array.ss_securitys)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server3)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
et_address.text = Utils.getEditable(vmess.address)
et_port.text = Utils.getEditable(vmess.port.toString())
et_id.text = Utils.getEditable(vmess.id)
val security = Utils.arrayFind(securitys, vmess.security)
if (security >= 0) {
sp_security.setSelection(security)
}
return true
}
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
et_address.text = null
et_port.text = Utils.getEditable("10086")
et_id.text = null
sp_security.setSelection(0)
return true
}
/**
* save server config
*/
fun saveServer(): Boolean {
val vmess: AngConfig.VmessBean
if (edit_index >= 0) {
vmess = configs.vmess[edit_index]
} else {
vmess = AngConfig.VmessBean()
}
vmess.guid = edit_guid
vmess.remarks = et_remarks.text.toString()
vmess.address = et_address.text.toString()
vmess.port = Utils.parseInt(et_port.text.toString())
vmess.id = et_id.text.toString()
vmess.security = securitys[sp_security.selectedItemPosition]
if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks)
return false
}
if (TextUtils.isEmpty(vmess.address)) {
toast(R.string.server_lab_address3)
return false
}
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
toast(R.string.server_lab_port3)
return false
}
if (TextUtils.isEmpty(vmess.id)) {
toast(R.string.server_lab_id3)
return false
}
if (AngConfigManager.addShadowsocksServer(vmess, edit_index) == 0) {
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
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()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -1,158 +0,0 @@
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.*
class Server4Activity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server4)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
et_address.text = Utils.getEditable(vmess.address)
et_port.text = Utils.getEditable(vmess.port.toString())
return true
}
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
et_address.text = null
et_port.text = Utils.getEditable("10086")
return true
}
/**
* save server config
*/
fun saveServer(): Boolean {
val vmess: AngConfig.VmessBean
if (edit_index >= 0) {
vmess = configs.vmess[edit_index]
} else {
vmess = AngConfig.VmessBean()
}
vmess.guid = edit_guid
vmess.remarks = et_remarks.text.toString()
vmess.address = et_address.text.toString()
vmess.port = Utils.parseInt(et_port.text.toString())
if (TextUtils.isEmpty(vmess.remarks)) {
toast(R.string.server_lab_remarks)
return false
}
if (TextUtils.isEmpty(vmess.address)) {
toast(R.string.server_lab_address3)
return false
}
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
toast(R.string.server_lab_port3)
return false
}
if (AngConfigManager.addSocksServer(vmess, edit_index) == 0) {
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
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()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
if (edit_index >= 0) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
}
} else {
del_config?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -1,54 +1,121 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import androidx.appcompat.app.AlertDialog
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.*
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_PORT
import com.v2ray.ang.dto.V2rayConfig.Companion.XTLS
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.MmkvManager.ID_MAIN
import com.v2ray.ang.util.MmkvManager.KEY_SELECTED_SERVER
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_server.*
class ServerActivity : BaseActivity() {
companion object {
private const val REQUEST_SCAN = 1
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy {
intent.getBooleanExtra("isRunning", false)
&& editGuid.isNotEmpty()
&& editGuid == mainStorage?.decodeString(KEY_SELECTED_SERVER)
}
private val createConfigType by lazy {
EConfigType.fromInt(intent.getIntExtra("createConfigType", EConfigType.VMESS.value)) ?: EConfigType.VMESS
}
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的服务器
private var edit_guid: String = ""
private var isRunning: Boolean = false
private val securitys: Array<out String> by lazy {
resources.getStringArray(R.array.securitys)
}
private val shadowsocksSecuritys: Array<out String> by lazy {
resources.getStringArray(R.array.ss_securitys)
}
private val flows: Array<out String> by lazy {
resources.getStringArray(R.array.flows)
}
private val networks: Array<out String> by lazy {
resources.getStringArray(R.array.networks)
}
private val headertypes: Array<out String> by lazy {
resources.getStringArray(R.array.headertypes)
private val tcpTypes: Array<out String> by lazy {
resources.getStringArray(R.array.header_type_tcp)
}
private val streamsecuritys: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecuritys)
private val kcpAndQuicTypes: Array<out String> by lazy {
resources.getStringArray(R.array.header_type_kcp_and_quic)
}
private val grpcModes: Array<out String> by lazy {
resources.getStringArray(R.array.mode_type_grpc)
}
private val streamSecuritys: Array<out String> by lazy {
resources.getStringArray(R.array.streamsecurityxs)
}
private val allowinsecures: Array<out String> by lazy {
resources.getStringArray(R.array.allowinsecures)
}
// Kotlin synthetics was used, but since it is removed in 1.8. We switch to old manual approach.
// We don't use AndroidViewBinding because, it is better to share similar logics for different
// protocols. Use findViewById manually ensures the xml are de-coupled with the activity logic.
private val et_remarks: EditText by lazy { findViewById(R.id.et_remarks) }
private val et_address: EditText by lazy { findViewById(R.id.et_address) }
private val et_port: EditText by lazy { findViewById(R.id.et_port) }
private val et_id: EditText by lazy { findViewById(R.id.et_id) }
//private val et_alterId: EditText? by lazy { findViewById(R.id.et_alterId) }
private val et_security: EditText? by lazy { findViewById(R.id.et_security) }
//private val sp_flow: Spinner? by lazy { findViewById(R.id.sp_flow) }
private val sp_security: Spinner? by lazy { findViewById(R.id.sp_security) }
private val sp_stream_security: Spinner? by lazy { findViewById(R.id.sp_stream_security) }
private val sp_allow_insecure: Spinner? by lazy { findViewById(R.id.sp_allow_insecure) }
//private val et_sni: EditText? by lazy { findViewById(R.id.et_sni) }
private val sp_network: Spinner? by lazy { findViewById(R.id.sp_network) }
private val sp_header_type: Spinner? by lazy { findViewById(R.id.sp_header_type) }
private val sp_header_type_title: TextView? by lazy { findViewById(R.id.sp_header_type_title) }
private val et_request_host: EditText? by lazy { findViewById(R.id.et_request_host) }
private val et_path: EditText? by lazy { findViewById(R.id.et_path) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_server)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
isRunning = intent.getBooleanExtra("isRunning", false)
title = getString(R.string.title_server)
if (edit_index >= 0) {
edit_guid = configs.vmess[edit_index].guid
bindingServer(configs.vmess[edit_index])
val config = MmkvManager.decodeServerConfig(editGuid)
when(config?.configType ?: createConfigType) {
EConfigType.VMESS -> setContentView(R.layout.activity_server_vmess)
EConfigType.CUSTOM -> return
EConfigType.SHADOWSOCKS -> setContentView(R.layout.activity_server_shadowsocks)
EConfigType.SOCKS -> setContentView(R.layout.activity_server_socks)
// EConfigType.VLESS -> setContentView(R.layout.activity_server_vless)
// EConfigType.TROJAN -> setContentView(R.layout.activity_server_trojan)
}
sp_network?.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val types = transportTypes(networks[position])
sp_header_type?.isEnabled = types.size > 1
val adapter = ArrayAdapter(this@ServerActivity, android.R.layout.simple_spinner_item, types)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
sp_header_type?.adapter = adapter
sp_header_type_title?.text = if (networks[position] == "grpc")
getString(R.string.server_lab_mode_type) else
getString(R.string.server_lab_head_type)
config?.getProxyOutbound()?.getTransportSettingDetails()?.let { transportDetails ->
sp_header_type?.setSelection(Utils.arrayFind(types, transportDetails[0]))
et_request_host?.text = Utils.getEditable(transportDetails[1])
et_path?.text = Utils.getEditable(transportDetails[2])
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
// do nothing
}
}
if (config != null) {
bindingServer(config)
} else {
clearServer()
}
@@ -58,33 +125,43 @@ class ServerActivity : BaseActivity() {
/**
* bingding seleced server config
*/
fun bindingServer(vmess: AngConfig.VmessBean): Boolean {
et_remarks.text = Utils.getEditable(vmess.remarks)
private fun bindingServer(config: ServerConfig): Boolean {
val outbound = config.getProxyOutbound() ?: return false
val streamSetting = config.outboundBean?.streamSettings ?: return false
et_address.text = Utils.getEditable(vmess.address)
et_port.text = Utils.getEditable(vmess.port.toString())
et_id.text = Utils.getEditable(vmess.id)
et_alterId.text = Utils.getEditable(vmess.alterId.toString())
val security = Utils.arrayFind(securitys, vmess.security)
et_remarks.text = Utils.getEditable(config.remarks)
et_address.text = Utils.getEditable(outbound.getServerAddress().orEmpty())
et_port.text = Utils.getEditable(outbound.getServerPort()?.toString() ?: DEFAULT_PORT.toString())
et_id.text = Utils.getEditable(outbound.getPassword().orEmpty())
if (config.configType == EConfigType.SOCKS) {
et_security?.text = Utils.getEditable(outbound.settings?.servers?.get(0)?.users?.get(0)?.user.orEmpty())
} else if (config.configType == EConfigType.VLESS) {
et_security?.text = Utils.getEditable(outbound.getSecurityEncryption().orEmpty())
val flow = Utils.arrayFind(flows, outbound.settings?.vnext?.get(0)?.users?.get(0)?.flow.orEmpty())
if (flow >= 0) {
//sp_flow.setSelection(flow)
}
}
val securityEncryptions = if (config.configType == EConfigType.SHADOWSOCKS) shadowsocksSecuritys else securitys
val security = Utils.arrayFind(securityEncryptions, outbound.getSecurityEncryption().orEmpty())
if (security >= 0) {
sp_security.setSelection(security)
}
val network = Utils.arrayFind(networks, vmess.network)
if (network >= 0) {
sp_network.setSelection(network)
sp_security?.setSelection(security)
}
val headerType = Utils.arrayFind(headertypes, vmess.headerType)
if (headerType >= 0) {
sp_header_type.setSelection(headerType)
}
et_request_host.text = Utils.getEditable(vmess.requestHost)
et_path.text = Utils.getEditable(vmess.path)
val streamSecurity = Utils.arrayFind(streamsecuritys, vmess.streamSecurity)
val streamSecurity = Utils.arrayFind(streamSecuritys, streamSetting.security)
if (streamSecurity >= 0) {
sp_stream_security.setSelection(streamSecurity)
sp_stream_security?.setSelection(streamSecurity)
(streamSetting.tlsSettings?: streamSetting.xtlsSettings)?.let { tlsSetting ->
val allowinsecure = Utils.arrayFind(allowinsecures, tlsSetting.allowInsecure.toString())
if (allowinsecure >= 0) {
sp_allow_insecure?.setSelection(allowinsecure)
}
et_request_host?.text = Utils.getEditable(tlsSetting.serverName)
}
}
val network = Utils.arrayFind(networks, streamSetting.network)
if (network >= 0) {
sp_network?.setSelection(network)
}
return true
}
@@ -92,112 +169,172 @@ class ServerActivity : BaseActivity() {
/**
* clear or init server config
*/
fun clearServer(): Boolean {
private fun clearServer(): Boolean {
et_remarks.text = null
et_address.text = null
et_port.text = Utils.getEditable("10086")
et_port.text = Utils.getEditable(DEFAULT_PORT.toString())
et_id.text = null
et_alterId.text = Utils.getEditable("64")
sp_security.setSelection(0)
sp_network.setSelection(0)
sp_security?.setSelection(0)
sp_network?.setSelection(0)
sp_header_type.setSelection(0)
et_request_host.text = null
et_path.text = null
sp_stream_security.setSelection(0)
sp_header_type?.setSelection(0)
et_request_host?.text = null
et_path?.text = null
sp_stream_security?.setSelection(0)
sp_allow_insecure?.setSelection(0)
//et_security.text = null
//sp_flow?.setSelection(0)
return true
}
/**
* save server config
*/
fun saveServer(): Boolean {
val vmess: AngConfig.VmessBean
if (edit_index >= 0) {
vmess = configs.vmess[edit_index]
} else {
vmess = AngConfig.VmessBean()
}
vmess.guid = edit_guid
vmess.remarks = et_remarks.text.toString()
vmess.address = et_address.text.toString()
vmess.port = Utils.parseInt(et_port.text.toString())
vmess.id = et_id.text.toString()
vmess.alterId = Utils.parseInt(et_alterId.text.toString())
vmess.security = securitys[sp_security.selectedItemPosition]
vmess.network = networks[sp_network.selectedItemPosition]
vmess.headerType = headertypes[sp_header_type.selectedItemPosition]
vmess.requestHost = et_request_host.text.toString()
vmess.path = et_path.text.toString()
vmess.streamSecurity = streamsecuritys[sp_stream_security.selectedItemPosition]
if (TextUtils.isEmpty(vmess.remarks)) {
private fun saveServer(): Boolean {
if (TextUtils.isEmpty(et_remarks.text.toString())) {
toast(R.string.server_lab_remarks)
return false
}
if (TextUtils.isEmpty(vmess.address)) {
if (TextUtils.isEmpty(et_address.text.toString())) {
toast(R.string.server_lab_address)
return false
}
if (TextUtils.isEmpty(vmess.port.toString()) || vmess.port <= 0) {
val port = Utils.parseInt(et_port.text.toString())
if (port <= 0) {
toast(R.string.server_lab_port)
return false
}
if (TextUtils.isEmpty(vmess.id)) {
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(createConfigType)
if (config.configType != EConfigType.SOCKS && TextUtils.isEmpty(et_id.text.toString())) {
toast(R.string.server_lab_id)
return false
}
if (TextUtils.isEmpty(vmess.alterId.toString()) || vmess.alterId < 0) {
toast(R.string.server_lab_alterid)
return false
config.remarks = et_remarks.text.toString().trim()
config.outboundBean?.settings?.vnext?.get(0)?.let { vnext ->
saveVnext(vnext, port, config)
}
config.outboundBean?.settings?.servers?.get(0)?.let { server ->
saveServers(server, port, config)
}
config.outboundBean?.streamSettings?.let {
saveStreamSettings(it)
}
if (AngConfigManager.addServer(vmess, edit_index) == 0) {
toast(R.string.toast_success)
finish()
return true
MmkvManager.encodeServerConfig(editGuid, config)
toast(R.string.toast_success)
finish()
return true
}
private fun saveVnext(vnext: V2rayConfig.OutboundBean.OutSettingsBean.VnextBean, port: Int, config: ServerConfig) {
vnext.address = et_address.text.toString().trim()
vnext.port = port
vnext.users[0].id = et_id.text.toString().trim()
if (config.configType == EConfigType.VMESS) {
vnext.users[0].security = securitys[sp_security?.selectedItemPosition ?: 0]
} else if (config.configType == EConfigType.VLESS) {
vnext.users[0].encryption = et_security?.text.toString().trim()
if (streamSecuritys[sp_stream_security?.selectedItemPosition ?: 0] == XTLS) {
// vnext.users[0].flow = flows[sp_flow.selectedItemPosition].ifBlank { V2rayConfig.DEFAULT_FLOW }
} else {
vnext.users[0].flow = ""
}
}
}
private fun saveServers(server: V2rayConfig.OutboundBean.OutSettingsBean.ServersBean, port: Int, config: ServerConfig) {
server.address = et_address.text.toString().trim()
server.port = port
if (config.configType == EConfigType.SHADOWSOCKS) {
server.password = et_id.text.toString().trim()
server.method = shadowsocksSecuritys[sp_security?.selectedItemPosition ?: 0]
} else if (config.configType == EConfigType.SOCKS) {
if (TextUtils.isEmpty(et_security?.text) && TextUtils.isEmpty(et_id.text)) {
server.users = null
} else {
val socksUsersBean = V2rayConfig.OutboundBean.OutSettingsBean.ServersBean.SocksUsersBean()
socksUsersBean.user = et_security?.text.toString().trim()
socksUsersBean.pass = et_id.text.toString().trim()
server.users = listOf(socksUsersBean)
}
} else if (config.configType == EConfigType.TROJAN) {
server.password = et_id.text.toString().trim()
}
}
private fun saveStreamSettings(streamSetting: V2rayConfig.OutboundBean.StreamSettingsBean) {
val network = sp_network?.selectedItemPosition ?: return
val type = sp_header_type?.selectedItemPosition ?: return
val requestHost = et_request_host?.text?.toString()?.trim() ?: return
val path = et_path?.text?.toString()?.trim() ?: return
//val sniField = et_sni?.text?.toString()?.trim() ?: return
val allowInsecureField = sp_allow_insecure?.selectedItemPosition ?: return
val streamSecurity = sp_stream_security?.selectedItemPosition ?: return
var sni = streamSetting.populateTransportSettings(
transport = networks[network],
headerType = transportTypes(networks[network])[type],
host = requestHost,
path = path,
seed = path,
quicSecurity = requestHost,
key = path,
mode = transportTypes(networks[network])[type],
serviceName = path
)
//if (sniField.isNotBlank()) {
// sni = sniField
//}
val allowInsecure = if (allowinsecures[allowInsecureField].isBlank()) {
false//settingsStorage?.decodeBool(PREF_ALLOW_INSECURE) ?: false
} else {
toast(R.string.toast_failure)
return false
allowinsecures[allowInsecureField].toBoolean()
}
streamSetting.populateTlsSettings(streamSecuritys[streamSecurity], allowInsecure, sni)
}
private fun transportTypes(network: String?): Array<out String> {
return if (network == "tcp") {
tcpTypes
} else if (network == "kcp" || network == "quic") {
kcpAndQuicTypes
} else if (network == "grpc") {
grpcModes
} else {
arrayOf("---")
}
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
private fun deleteServer(): Boolean {
if (editGuid.isNotEmpty()) {
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)
}
MmkvManager.removeServer(editGuid)
finish()
}
.show()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
val delButton = menu.findItem(R.id.del_config)
val saveButton = menu.findItem(R.id.save_config)
if (edit_index >= 0) {
if (editGuid.isNotEmpty()) {
if (isRunning) {
if (edit_index == configs.index) {
del_config?.isVisible = false
save_config?.isVisible = false
}
delButton?.isVisible = false
saveButton?.isVisible = false
}
} else {
del_config?.isVisible = false
delButton?.isVisible = false
}
return super.onCreateOptionsMenu(menu)

View File

@@ -0,0 +1,144 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.blacksquircle.ui.language.json.JsonLanguage
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R
import com.v2ray.ang.databinding.ActivityServerCustomConfigBinding
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import me.drakeet.support.toast.ToastCompat
class ServerCustomConfigActivity : BaseActivity() {
private lateinit var binding: ActivityServerCustomConfigBinding
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val editGuid by lazy { intent.getStringExtra("guid").orEmpty() }
private val isRunning by lazy {
intent.getBooleanExtra("isRunning", false)
&& editGuid.isNotEmpty()
&& editGuid == mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityServerCustomConfigBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_server)
binding.editor.language = JsonLanguage()
val config = MmkvManager.decodeServerConfig(editGuid)
if (config != null) {
bindingServer(config)
} else {
clearServer()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
/**
* bingding seleced server config
*/
private fun bindingServer(config: ServerConfig): Boolean {
binding.etRemarks.text = Utils.getEditable(config.remarks)
val raw = serverRawStorage?.decodeString(editGuid)
if (raw.isNullOrBlank()) {
binding.editor.setTextContent(Utils.getEditable(config.fullConfig?.toPrettyPrinting().orEmpty()))
} else {
binding.editor.setTextContent(Utils.getEditable(raw))
}
return true
}
/**
* clear or init server config
*/
private fun clearServer(): Boolean {
binding.etRemarks.text = null
return true
}
/**
* save server config
*/
private fun saveServer(): Boolean {
if (TextUtils.isEmpty(binding.etRemarks.text.toString())) {
toast(R.string.server_lab_remarks)
return false
}
val v2rayConfig = try {
Gson().fromJson(binding.editor.text.toString(), V2rayConfig::class.java)
} catch (e: Exception) {
e.printStackTrace()
ToastCompat.makeText(this, "${getString(R.string.toast_malformed_josn)} ${e.cause?.message}", Toast.LENGTH_LONG).show()
return false
}
val config = MmkvManager.decodeServerConfig(editGuid) ?: ServerConfig.create(EConfigType.CUSTOM)
config.remarks = binding.etRemarks.text.toString().trim()
config.fullConfig = v2rayConfig
MmkvManager.encodeServerConfig(editGuid, config)
serverRawStorage?.encode(editGuid, binding.editor.text.toString())
toast(R.string.toast_success)
finish()
return true
}
/**
* save server config
*/
private fun deleteServer(): Boolean {
if (editGuid.isNotEmpty()) {
AlertDialog.Builder(this).setMessage(R.string.del_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
MmkvManager.removeServer(editGuid)
finish()
}
.show()
}
return true
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
val delButton = menu.findItem(R.id.del_config)
val saveButton = menu.findItem(R.id.save_config)
if (editGuid.isNotEmpty()) {
if (isRunning) {
delButton?.isVisible = false
saveButton?.isVisible = false
}
} else {
delButton?.isVisible = false
}
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.del_config -> {
deleteServer()
true
}
R.id.save_config -> {
saveServer()
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -2,39 +2,19 @@ package com.v2ray.ang.ui
import android.content.Intent
import android.os.Bundle
import android.support.v7.preference.*
import androidx.preference.*
import android.text.TextUtils
import android.view.View
import androidx.activity.viewModels
import com.v2ray.ang.R
import com.v2ray.ang.AppConfig
import com.v2ray.ang.extension.toast
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.util.Utils
import com.v2ray.ang.viewmodel.SettingsViewModel
class SettingsActivity : BaseActivity() {
companion object {
// const val PREF_BYPASS_MAINLAND = "pref_bypass_mainland"
// const val PREF_START_ON_BOOT = "pref_start_on_boot"
const val PREF_PER_APP_PROXY = "pref_per_app_proxy"
// const val PREF_MUX_ENAimport libv2ray.Libv2rayBLED = "pref_mux_enabled"
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
const val PREF_SNIFFING_ENABLED = "pref_sniffing_enabled"
const val PREF_PROXY_SHARING = "pref_proxy_sharing_enabled"
const val PREF_LOCAL_DNS_ENABLED = "pref_local_dns_enabled"
const val PREF_REMOTE_DNS = "pref_remote_dns"
const val PREF_DOMESTIC_DNS = "pref_domestic_dns"
// const val PREF_SOCKS_PORT = "pref_socks_port"
// const val PREF_HTTP_PORT = "pref_http_port"
const val PREF_ROUTING_DOMAIN_STRATEGY = "pref_routing_domain_strategy"
const val PREF_ROUTING_MODE = "pref_routing_mode"
const val PREF_ROUTING_CUSTOM = "pref_routing_custom"
// const val PREF_DONATE = "pref_donate"
// const val PREF_LICENSES = "pref_licenses"
// const val PREF_FEEDBACK = "pref_feedback"
// const val PREF_TG_GROUP = "pref_tg_group"
// const val PREF_AUTO_RESTART = "pref_auto_restart"
const val PREF_FORWARD_IPV6 = "pref_forward_ipv6"
}
private val settingsViewModel: SettingsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -43,20 +23,26 @@ class SettingsActivity : BaseActivity() {
title = getString(R.string.title_settings)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
settingsViewModel.startListenPreferenceChange()
}
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 }
private val perAppProxy by lazy { findPreference(AppConfig.PREF_PER_APP_PROXY) as CheckBoxPreference }
private val localDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) }
private val fakeDns by lazy { findPreference(AppConfig.PREF_FAKE_DNS_ENABLED) }
private val localDnsPort by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_PORT) }
private val vpnDns by lazy { findPreference(AppConfig.PREF_VPN_DNS) }
private val sppedEnabled by lazy { findPreference(AppConfig.PREF_SPEED_ENABLED) as CheckBoxPreference }
private val sniffingEnabled by lazy { findPreference(AppConfig.PREF_SNIFFING_ENABLED) as CheckBoxPreference }
private val proxySharing by lazy { findPreference(AppConfig.PREF_PROXY_SHARING) as CheckBoxPreference }
private val domainStrategy by lazy { findPreference(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY) as ListPreference }
private val routingMode by lazy { findPreference(AppConfig.PREF_ROUTING_MODE) as ListPreference }
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 }
private val forwardIpv6 by lazy { findPreference(AppConfig.PREF_FORWARD_IPV6) as CheckBoxPreference }
private val enableLocalDns by lazy { findPreference(AppConfig.PREF_LOCAL_DNS_ENABLED) as CheckBoxPreference }
private val domesticDns by lazy { findPreference(AppConfig.PREF_DOMESTIC_DNS) as EditTextPreference }
private val remoteDns by lazy { findPreference(AppConfig.PREF_REMOTE_DNS) as EditTextPreference }
// val autoRestart by lazy { findPreference(PREF_AUTO_RESTART) as CheckBoxPreference }
@@ -64,7 +50,7 @@ class SettingsActivity : BaseActivity() {
// val socksPort by lazy { findPreference(PREF_SOCKS_PORT) as EditTextPreference }
// val httpPort by lazy { findPreference(PREF_HTTP_PORT) as EditTextPreference }
private val routingCustom: Preference by lazy { findPreference(PREF_ROUTING_CUSTOM) }
private val routingCustom: Preference by lazy { findPreference(AppConfig.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) }
@@ -74,7 +60,7 @@ class SettingsActivity : BaseActivity() {
private fun restartProxy() {
Utils.stopVService(requireContext())
Utils.startVService(requireContext())
V2RayServiceManager.startV2Ray(requireContext())
}
private fun isRunning(): Boolean {
@@ -159,9 +145,21 @@ class SettingsActivity : BaseActivity() {
restartProxy()
true
}
localDns?.setOnPreferenceChangeListener{ _, any ->
updateLocalDns(any as Boolean)
true
}
localDnsPort?.setOnPreferenceChangeListener { _, any ->
val nval = any as String
localDnsPort?.summary = if (TextUtils.isEmpty(nval)) "10807" else nval
true
}
vpnDns?.setOnPreferenceChangeListener { _, any ->
vpnDns?.summary = any as String
true
}
mode.setOnPreferenceChangeListener { _, newValue ->
updatePerAppProxy(newValue.toString())
updateMode(newValue.toString())
true
}
mode.dialogLayoutResource = R.layout.preference_with_help_link
@@ -205,33 +203,44 @@ class SettingsActivity : BaseActivity() {
override fun onStart() {
super.onStart()
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, "")
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
updateMode(defaultSharedPreferences.getString(AppConfig.PREF_MODE, "VPN"))
var remoteDnsString = defaultSharedPreferences.getString(AppConfig.PREF_REMOTE_DNS, "")
domesticDns.summary = defaultSharedPreferences.getString(AppConfig.PREF_DOMESTIC_DNS, "")
if (remoteDns.summary == "") {
remoteDns.summary = AppConfig.DNS_AGENT
if (TextUtils.isEmpty(remoteDnsString)) {
remoteDnsString = AppConfig.DNS_AGENT
}
if ( domesticDns.summary == "") {
domesticDns.summary = AppConfig.DNS_DIRECT
}
remoteDns.summary = remoteDnsString
vpnDns?.summary = defaultSharedPreferences.getString(AppConfig.PREF_VPN_DNS, remoteDnsString)
// socksPort.summary = defaultSharedPreferences.getString(PREF_SOCKS_PORT, "10808")
// lanconnPort.summary = defaultSharedPreferences.getString(PREF_HTTP_PORT, "")
}
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
private fun updateMode(mode: String?) {
val defaultSharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity())
val vpn = mode == "VPN"
perAppProxy.isEnabled = vpn
perAppProxy.isChecked = PreferenceManager.getDefaultSharedPreferences(requireActivity())
.getBoolean(AppConfig.PREF_PER_APP_PROXY, false)
localDns?.isEnabled = vpn
fakeDns?.isEnabled = vpn
localDnsPort?.isEnabled = vpn
vpnDns?.isEnabled = vpn
if (vpn) {
updateLocalDns(defaultSharedPreferences.getBoolean(AppConfig.PREF_LOCAL_DNS_ENABLED, false))
}
}
private fun updateLocalDns(enabled: Boolean) {
fakeDns?.isEnabled = enabled
localDnsPort?.isEnabled = enabled
vpnDns?.isEnabled = !enabled
}
}
fun onModeHelpClicked(view: View) {

View File

@@ -1,36 +1,38 @@
package com.v2ray.ang.ui
import android.os.Bundle
import android.support.v7.app.AlertDialog
import androidx.appcompat.app.AlertDialog
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.R
import com.v2ray.ang.dto.AngConfig
import com.v2ray.ang.databinding.ActivitySubEditBinding
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.extension.toast
import com.v2ray.ang.util.AngConfigManager
import com.v2ray.ang.util.MmkvManager
import com.v2ray.ang.util.Utils
import kotlinx.android.synthetic.main.activity_sub_edit.*
class SubEditActivity : BaseActivity() {
private lateinit var binding: ActivitySubEditBinding
var del_config: MenuItem? = null
var save_config: MenuItem? = null
private lateinit var configs: AngConfig
private var edit_index: Int = -1 //当前编辑的
private val subStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SUB, MMKV.MULTI_PROCESS_MODE) }
private val editSubId by lazy { intent.getStringExtra("subId").orEmpty() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub_edit)
configs = AngConfigManager.configs
edit_index = intent.getIntExtra("position", -1)
binding = ActivitySubEditBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_sub_setting)
if (edit_index >= 0) {
bindingServer(configs.subItem[edit_index])
val json = subStorage?.decodeString(editSubId)
if (!json.isNullOrBlank()) {
bindingServer(Gson().fromJson(json, SubscriptionItem::class.java))
} else {
clearServer()
}
@@ -40,9 +42,9 @@ class SubEditActivity : BaseActivity() {
/**
* bingding seleced server config
*/
fun bindingServer(subItem: AngConfig.SubItemBean): Boolean {
et_remarks.text = Utils.getEditable(subItem.remarks)
et_url.text = Utils.getEditable(subItem.url)
private fun bindingServer(subItem: SubscriptionItem): Boolean {
binding.etRemarks.text = Utils.getEditable(subItem.remarks)
binding.etUrl.text = Utils.getEditable(subItem.url)
return true
}
@@ -50,9 +52,9 @@ class SubEditActivity : BaseActivity() {
/**
* clear or init server config
*/
fun clearServer(): Boolean {
et_remarks.text = null
et_url.text = null
private fun clearServer(): Boolean {
binding.etRemarks.text = null
binding.etUrl.text = null
return true
}
@@ -60,16 +62,19 @@ class SubEditActivity : BaseActivity() {
/**
* save server config
*/
fun saveServer(): Boolean {
val subItem: AngConfig.SubItemBean
if (edit_index >= 0) {
subItem = configs.subItem[edit_index]
private fun saveServer(): Boolean {
val subItem: SubscriptionItem
val json = subStorage?.decodeString(editSubId)
var subId = editSubId
if (!json.isNullOrBlank()) {
subItem = Gson().fromJson(json, SubscriptionItem::class.java)
} else {
subItem = AngConfig.SubItemBean()
subId = Utils.getUuid()
subItem = SubscriptionItem()
}
subItem.remarks = et_remarks.text.toString()
subItem.url = et_url.text.toString()
subItem.remarks = binding.etRemarks.text.toString()
subItem.url = binding.etUrl.text.toString()
if (TextUtils.isEmpty(subItem.remarks)) {
toast(R.string.sub_setting_remarks)
@@ -80,43 +85,33 @@ class SubEditActivity : BaseActivity() {
return false
}
if (AngConfigManager.addSubItem(subItem, edit_index) == 0) {
toast(R.string.toast_success)
finish()
return true
} else {
toast(R.string.toast_failure)
return false
}
subStorage?.encode(subId, Gson().toJson(subItem))
toast(R.string.toast_success)
finish()
return true
}
/**
* save server config
*/
fun deleteServer(): Boolean {
if (edit_index >= 0) {
private fun deleteServer(): Boolean {
if (editSubId.isNotEmpty()) {
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)
}
MmkvManager.removeSubscription(editSubId)
finish()
}
.show()
} else {
}
return true
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
del_config = menu?.findItem(R.id.del_config)
save_config = menu?.findItem(R.id.save_config)
del_config = menu.findItem(R.id.del_config)
save_config = menu.findItem(R.id.save_config)
if (edit_index >= 0) {
} else {
if (editSubId.isEmpty()) {
del_config?.isVisible = false
}

View File

@@ -1,53 +1,55 @@
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 androidx.recyclerview.widget.LinearLayoutManager
import com.v2ray.ang.databinding.ActivitySubSettingBinding
import com.v2ray.ang.dto.SubscriptionItem
import com.v2ray.ang.util.MmkvManager
class SubSettingActivity : BaseActivity() {
private lateinit var binding: ActivitySubSettingBinding
var subscriptions:List<Pair<String, SubscriptionItem>> = listOf()
private val adapter by lazy { SubSettingRecyclerAdapter(this) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sub_setting)
binding = ActivitySubSettingBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
title = getString(R.string.title_sub_setting)
recycler_view.setHasFixedSize(true)
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onResume() {
super.onResume()
adapter.updateConfigList()
subscriptions = MmkvManager.decodeSubscriptions()
adapter.notifyDataSetChanged()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_sub_setting, menu)
menu?.findItem(R.id.del_config)?.isVisible = false
menu?.findItem(R.id.save_config)?.isVisible = false
menu.findItem(R.id.del_config)?.isVisible = false
menu.findItem(R.id.save_config)?.isVisible = false
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.add_config -> {
startActivity(Intent(this, SubEditActivity::class.java)
.putExtra("position", -1)
)
adapter.updateConfigList()
startActivity(Intent(this, SubEditActivity::class.java))
true
}
else -> super.onOptionsItemSelected(item)
}
}

View File

@@ -2,64 +2,34 @@ package com.v2ray.ang.ui
import android.content.Intent
import android.graphics.Color
import android.support.v7.widget.RecyclerView
import androidx.recyclerview.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 com.v2ray.ang.databinding.ItemRecyclerSubSettingBinding
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.BaseViewHolder>() {
class SubSettingRecyclerAdapter(val activity: SubSettingActivity) : RecyclerView.Adapter<SubSettingRecyclerAdapter.MainViewHolder>() {
private var mActivity: SubSettingActivity = activity
private lateinit var configs: AngConfig
init {
updateConfigList()
}
override fun getItemCount() = mActivity.subscriptions.size
override fun getItemCount() = configs.subItem.count()
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
val subId = mActivity.subscriptions[position].first
val subItem = mActivity.subscriptions[position].second
holder.itemSubSettingBinding.tvName.text = subItem.remarks
holder.itemSubSettingBinding.tvUrl.text = subItem.url
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
if (holder is MainViewHolder) {
val remarks = configs.subItem[position].remarks
val url = configs.subItem[position].url
holder.name.text = remarks
holder.url.text = url
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
holder.layout_edit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
.putExtra("position", position)
)
}
} else {
holder.itemSubSettingBinding.layoutEdit.setOnClickListener {
mActivity.startActivity(Intent(mActivity, SubEditActivity::class.java)
.putExtra("subId", subId)
)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
return MainViewHolder(LayoutInflater.from(parent.context)
.inflate(R.layout.item_recycler_sub_setting, parent, false))
}
fun updateConfigList() {
configs = AngConfigManager.configs
notifyDataSetChanged()
}
// fun updateSelectedItem() {
// notifyItemChanged(configs.index)
// }
open class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
class MainViewHolder(itemView: View) : BaseViewHolder(itemView) {
val name = itemView.tv_name!!
val url = itemView.tv_url!!
val layout_edit = itemView.layout_edit!!
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
return MainViewHolder(ItemRecyclerSubSettingBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
class MainViewHolder(val itemSubSettingBinding: ItemRecyclerSubSettingBinding) : RecyclerView.ViewHolder(itemSubSettingBinding.root)
}

View File

@@ -7,32 +7,40 @@ import android.widget.ArrayAdapter
import android.widget.ListView
import java.util.ArrayList
import com.v2ray.ang.R
import com.v2ray.ang.util.AngConfigManager
import android.content.Intent
import android.text.TextUtils
import android.view.Menu
import android.view.MenuItem
import com.google.zxing.WriterException
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import kotlinx.android.synthetic.main.activity_tasker.*
import com.v2ray.ang.databinding.ActivityTaskerBinding
import com.v2ray.ang.util.MmkvManager
class TaskerActivity : BaseActivity() {
private lateinit var binding: ActivityTaskerBinding
private var listview: ListView? = null
private var lstData: ArrayList<String> = ArrayList()
private var lstGuid: ArrayList<String> = ArrayList()
private val serverStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tasker)
binding = ActivityTaskerBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
//add def value
lstData.add("Default")
lstGuid.add(AppConfig.TASKER_DEFAULT_GUID)
AngConfigManager.configs.vmess.forEach {
lstData.add(it.remarks)
lstGuid.add(it.guid)
serverStorage?.allKeys()?.forEach { key ->
MmkvManager.decodeServerConfig(key)?.let { config ->
lstData.add(config.remarks)
lstGuid.add(key)
}
}
val adapter = ArrayAdapter(this,
android.R.layout.simple_list_item_single_choice, lstData)
@@ -51,7 +59,7 @@ class TaskerActivity : BaseActivity() {
if (switch == null || TextUtils.isEmpty(guid)) {
return
} else {
switch_start_service.isChecked = switch
binding.switchStartService.isChecked = switch
val pos = lstGuid.indexOf(guid.toString())
if (pos >= 0) {
listview?.setItemChecked(pos, true)
@@ -70,17 +78,15 @@ class TaskerActivity : BaseActivity() {
}
val extraBundle = Bundle()
extraBundle.putBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, switch_start_service.isChecked)
extraBundle.putBoolean(AppConfig.TASKER_EXTRA_BUNDLE_SWITCH, binding.switchStartService.isChecked)
extraBundle.putString(AppConfig.TASKER_EXTRA_BUNDLE_GUID, lstGuid[position])
val intent = Intent()
val remarks = lstData[position]
var blurb = ""
if (switch_start_service.isChecked) {
blurb = "Start $remarks"
val blurb = if (binding.switchStartService.isChecked) {
"Start $remarks"
} else {
blurb = "Stop $remarks"
"Stop $remarks"
}
intent.putExtra(AppConfig.TASKER_EXTRA_BUNDLE, extraBundle)
@@ -89,9 +95,9 @@ class TaskerActivity : BaseActivity() {
finish()
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.action_server, menu)
val del_config = menu?.findItem(R.id.del_config)
val del_config = menu.findItem(R.id.del_config)
del_config?.isVisible = false
return super.onCreateOptionsMenu(menu)
}
@@ -108,4 +114,3 @@ class TaskerActivity : BaseActivity() {
}
}

View File

@@ -31,7 +31,7 @@ object AppManagerUtil {
return apps
}
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.create {
fun rxLoadNetworkAppList(ctx: Context): Observable<ArrayList<AppInfo>> = Observable.unsafeCreate {
it.onNext(loadNetworkAppList(ctx))
}

View File

@@ -0,0 +1,144 @@
package com.v2ray.ang.util
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
import com.v2ray.ang.dto.ServerAffiliationInfo
import com.v2ray.ang.dto.ServerConfig
import com.v2ray.ang.dto.SubscriptionItem
object MmkvManager {
const val ID_MAIN = "MAIN"
const val ID_SERVER_CONFIG = "SERVER_CONFIG"
const val ID_SERVER_RAW = "SERVER_RAW"
const val ID_SERVER_AFF = "SERVER_AFF"
const val ID_SUB = "SUB"
const val ID_SETTING = "SETTING"
const val KEY_SELECTED_SERVER = "SELECTED_SERVER"
const val KEY_ANG_CONFIGS = "ANG_CONFIGS"
private val mainStorage by lazy { MMKV.mmkvWithID(ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val serverStorage by lazy { MMKV.mmkvWithID(ID_SERVER_CONFIG, MMKV.MULTI_PROCESS_MODE) }
private val serverAffStorage by lazy { MMKV.mmkvWithID(ID_SERVER_AFF, MMKV.MULTI_PROCESS_MODE) }
private val subStorage by lazy { MMKV.mmkvWithID(ID_SUB, MMKV.MULTI_PROCESS_MODE) }
fun decodeServerList(): MutableList<String> {
val json = mainStorage?.decodeString(KEY_ANG_CONFIGS)
return if (json.isNullOrBlank()) {
mutableListOf()
} else {
Gson().fromJson(json, Array<String>::class.java).toMutableList()
}
}
fun decodeServerConfig(guid: String): ServerConfig? {
if (guid.isBlank()) {
return null
}
val json = serverStorage?.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return Gson().fromJson(json, ServerConfig::class.java)
}
fun encodeServerConfig(guid: String, config: ServerConfig): String {
val key = guid.ifBlank { Utils.getUuid() }
serverStorage?.encode(key, Gson().toJson(config))
val serverList= decodeServerList()
if (!serverList.contains(key)) {
serverList.add(key)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
if (mainStorage?.decodeString(KEY_SELECTED_SERVER).isNullOrBlank()) {
mainStorage?.encode(KEY_SELECTED_SERVER, key)
}
}
return key
}
fun removeServer(guid: String) {
if (guid.isBlank()) {
return
}
if (mainStorage?.decodeString(KEY_SELECTED_SERVER) == guid) {
mainStorage?.remove(KEY_SELECTED_SERVER)
}
val serverList= decodeServerList()
serverList.remove(guid)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
serverStorage?.remove(guid)
serverAffStorage?.remove(guid)
}
fun removeServerViaSubid(subid: String) {
if (subid.isBlank()) {
return
}
serverStorage?.allKeys()?.forEach { key ->
decodeServerConfig(key)?.let { config ->
if (config.subscriptionId == subid) {
removeServer(key)
}
}
}
}
fun decodeServerAffiliationInfo(guid: String): ServerAffiliationInfo? {
if (guid.isBlank()) {
return null
}
val json = serverAffStorage?.decodeString(guid)
if (json.isNullOrBlank()) {
return null
}
return Gson().fromJson(json, ServerAffiliationInfo::class.java)
}
fun encodeServerTestDelayMillis(guid: String, testResult: Long) {
if (guid.isBlank()) {
return
}
val aff = decodeServerAffiliationInfo(guid) ?: ServerAffiliationInfo()
aff.testDelayMillis = testResult
serverAffStorage?.encode(guid, Gson().toJson(aff))
}
fun clearAllTestDelayResults() {
serverAffStorage?.allKeys()?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff ->
aff.testDelayMillis = 0
serverAffStorage?.encode(key, Gson().toJson(aff))
}
}
}
fun importUrlAsSubscription(url: String): Int {
val subscriptions = decodeSubscriptions()
subscriptions.forEach {
if (it.second.url == url) {
return 0
}
}
val subItem = SubscriptionItem()
subItem.remarks = "import sub"
subItem.url = url
subStorage?.encode(Utils.getUuid(), Gson().toJson(subItem))
return 1
}
fun decodeSubscriptions(): List<Pair<String, SubscriptionItem>> {
val subscriptions = mutableListOf<Pair<String, SubscriptionItem>>()
subStorage?.allKeys()?.forEach { key ->
val json = subStorage?.decodeString(key)
if (!json.isNullOrBlank()) {
subscriptions.add(Pair(key, Gson().fromJson(json, SubscriptionItem::class.java)))
}
}
subscriptions.sortedBy { (_, value) -> value.addedTime }
return subscriptions
}
fun removeSubscription(subid: String) {
subStorage?.remove(subid)
removeServerViaSubid(subid)
}
}

View File

@@ -15,29 +15,26 @@ import android.content.ClipData
import android.content.Intent
import android.net.Uri
import android.os.SystemClock
import android.text.TextUtils
import android.util.Log
import android.util.Patterns
import android.webkit.URLUtil
import com.v2ray.ang.AngApplication
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.BuildConfig
import com.v2ray.ang.R
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.responseLength
import com.v2ray.ang.extension.toast
import com.v2ray.ang.extension.v2RayApplication
import com.v2ray.ang.service.V2RayServiceManager
import com.v2ray.ang.ui.SettingsActivity
import kotlinx.coroutines.isActive
import me.dozen.dpreference.DPreference
import java.io.IOException
import java.net.*
import libv2ray.Libv2ray
import kotlin.coroutines.coroutineContext
object Utils {
val tcpTestingSockets = ArrayList<Socket?>()
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
private val tcpTestingSockets = ArrayList<Socket?>()
/**
* convert string to editalbe for kotlin
@@ -65,11 +62,11 @@ object Utils {
* parseInt
*/
fun parseInt(str: String): Int {
try {
return Integer.parseInt(str)
return try {
Integer.parseInt(str)
} catch (e: Exception) {
e.printStackTrace()
return 0
0
}
}
@@ -77,12 +74,12 @@ object Utils {
* get text from clipboard
*/
fun getClipboard(context: Context): String {
try {
return try {
val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
return cmb.primaryClip?.getItemAt(0)?.text.toString()
cmb.primaryClip?.getItemAt(0)?.text.toString()
} catch (e: Exception) {
e.printStackTrace()
return ""
""
}
}
@@ -103,64 +100,68 @@ object Utils {
* base64 decode
*/
fun decode(text: String): String {
tryDecodeBase64(text)?.let { return it }
if (text.endsWith('=')) {
// try again for some loosely formatted base64
tryDecodeBase64(text.trimEnd('='))?.let { return it }
}
return ""
}
fun tryDecodeBase64(text: String): String? {
try {
return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8"))
} catch (e: Exception) {
e.printStackTrace()
return ""
Log.i(AppConfig.ANG_PACKAGE, "Parse base64 standard failed $e")
}
try {
return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset("UTF-8"))
} catch (e: Exception) {
Log.i(AppConfig.ANG_PACKAGE, "Parse base64 url safe failed $e")
}
return null
}
/**
* base64 encode
*/
fun encode(text: String): String {
try {
return Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
return try {
Base64.encodeToString(text.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
} catch (e: Exception) {
e.printStackTrace()
return ""
""
}
}
/**
* get remote dns servers from preference
*/
fun getRemoteDnsServers(defaultDPreference: DPreference): ArrayList<String> {
val remoteDns = defaultDPreference.getPrefString(SettingsActivity.PREF_REMOTE_DNS, AppConfig.DNS_AGENT)
val ret = ArrayList<String>()
if (!TextUtils.isEmpty(remoteDns)) {
remoteDns
.split(",")
.forEach {
if (Utils.isPureIpAddress(it)) {
ret.add(it)
}
}
}
if (ret.size == 0) {
ret.add(AppConfig.DNS_AGENT)
fun getRemoteDnsServers(): List<String> {
val remoteDns = settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS) ?: AppConfig.DNS_AGENT
val ret = remoteDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_AGENT)
}
return ret
}
fun getVpnDnsServers(): List<String> {
val vpnDns = settingsStorage?.decodeString(AppConfig.PREF_VPN_DNS)
?: settingsStorage?.decodeString(AppConfig.PREF_REMOTE_DNS)
?: AppConfig.DNS_AGENT
return vpnDns.split(",").filter { isPureIpAddress(it) }
// allow empty, in that case dns will use system default
}
/**
* get remote dns servers from preference
*/
fun getDomesticDnsServers(defaultDPreference: DPreference): ArrayList<String> {
val domesticDns = defaultDPreference.getPrefString(SettingsActivity.PREF_DOMESTIC_DNS, AppConfig.DNS_DIRECT)
val ret = ArrayList<String>()
if (!TextUtils.isEmpty(domesticDns)) {
domesticDns
.split(",")
.forEach {
if (Utils.isPureIpAddress(it)) {
ret.add(it)
}
}
}
if (ret.size == 0) {
ret.add(AppConfig.DNS_DIRECT)
fun getDomesticDnsServers(): List<String> {
val domesticDns = settingsStorage?.decodeString(AppConfig.PREF_DOMESTIC_DNS) ?: AppConfig.DNS_DIRECT
val ret = domesticDns.split(",").filter { isPureIpAddress(it) || isCoreDNSAddress(it) }
if (ret.isEmpty()) {
return listOf(AppConfig.DNS_DIRECT)
}
return ret
}
@@ -171,12 +172,12 @@ object Utils {
fun createQRCode(text: String, size: Int = 800): Bitmap? {
try {
val hints = HashMap<EncodeHintType, String>()
hints.put(EncodeHintType.CHARACTER_SET, "utf-8")
hints[EncodeHintType.CHARACTER_SET] = "utf-8"
val bitMatrix = QRCodeWriter().encode(text,
BarcodeFormat.QR_CODE, size, size, hints)
val pixels = IntArray(size * size)
for (y in 0..size - 1) {
for (x in 0..size - 1) {
for (y in 0 until size) {
for (x in 0 until size) {
if (bitMatrix.get(x, y)) {
pixels[y * size + x] = 0xff000000.toInt()
} else {
@@ -221,7 +222,7 @@ object Utils {
}
// addr = addr.toLowerCase()
var octets = addr.split('.').toTypedArray()
val octets = addr.split('.').toTypedArray()
if (octets.size == 4) {
if(octets[3].indexOf(":") > 0) {
addr = addr.substring(0, addr.indexOf(":"))
@@ -256,12 +257,16 @@ object Utils {
return regV6.matches(addr)
}
private fun isCoreDNSAddress(s: String): Boolean {
return s.startsWith("https") || s.startsWith("tcp") || s.startsWith("quic")
}
/**
* is valid url
*/
fun isValidUrl(value: String?): Boolean {
try {
if (Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
if (value != null && Patterns.WEB_URL.matcher(value).matches() || URLUtil.isValidUrl(value)) {
return true
}
} catch (e: WriterException) {
@@ -272,55 +277,12 @@ object Utils {
}
fun startVServiceFromToggle(context: Context): Boolean {
val result = startVService(context)
if (!result) {
if (mainStorage?.decodeString(MmkvManager.KEY_SELECTED_SERVER).isNullOrEmpty()) {
context.toast(R.string.app_tile_first_use)
}
return result
}
/**
* startVService
*/
fun startVService(context: Context): Boolean {
if (context.v2RayApplication.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
context.toast(R.string.toast_warning_pref_proxysharing_short)
}else{
context.toast(R.string.toast_services_start)
}
if (AngConfigManager.genStoreV2rayConfig(-1)) {
val configContent = AngConfigManager.currGeneratedV2rayConfig()
val configType = AngConfigManager.currConfigType()
if (configType == EConfigType.CUSTOM) {
try {
Libv2ray.testConfig(configContent)
} catch (e: Exception) {
context.toast(e.toString())
return false
}
}
V2RayServiceManager.startV2Ray(context, context.v2RayApplication.defaultDPreference.getPrefString(AppConfig.PREF_MODE, "VPN"))
return true
} else {
return false
}
}
/**
* startVService
*/
fun startVService(context: Context, guid: String): Boolean {
val index = AngConfigManager.getIndexViaGuid(guid)
context.v2RayApplication.curIndex=index
return startVService(context, index)
}
/**
* startVService
*/
fun startVService(context: Context, index: Int): Boolean {
AngConfigManager.setActiveServer(index)
return startVService(context)
V2RayServiceManager.startV2Ray(context)
return true
}
/**
@@ -340,29 +302,29 @@ object Utils {
* uuid
*/
fun getUuid(): String {
try {
return UUID.randomUUID().toString().replace("-", "")
return try {
UUID.randomUUID().toString().replace("-", "")
} catch (e: Exception) {
e.printStackTrace()
return ""
""
}
}
fun urlDecode(url: String): String {
try {
return URLDecoder.decode(url, "UTF-8")
return try {
URLDecoder.decode(url, "UTF-8")
} catch (e: Exception) {
e.printStackTrace()
return url
url
}
}
fun urlEncode(url: String): String {
try {
return URLEncoder.encode(url, "UTF-8")
return try {
URLEncoder.encode(url, "UTF-8")
} catch (e: Exception) {
e.printStackTrace()
return url
url
}
}
@@ -422,8 +384,8 @@ object Utils {
/**
* readTextFromAssets
*/
fun readTextFromAssets(app: AngApplication, fileName: String): String {
val content = app.assets.open(fileName).bufferedReader().use {
fun readTextFromAssets(context: Context, fileName: String): String {
val content = context.assets.open(fileName).bufferedReader().use {
it.readText()
}
return content
@@ -437,9 +399,9 @@ object Utils {
val command = "/system/bin/ping -c 3 $url"
val process = Runtime.getRuntime().exec(command)
val allText = process.inputStream.bufferedReader().use { it.readText() }
if (!TextUtils.isEmpty(allText)) {
if (allText.isNotBlank()) {
val tempInfo = allText.substring(allText.indexOf("min/avg/max/mdev") + 19)
val temps = tempInfo.split("/".toRegex()).dropLastWhile({ it.isEmpty() }).toTypedArray()
val temps = tempInfo.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (temps.count() > 0 && temps[0].length < 10) {
return temps[0].toFloat().toInt().toString() + "ms"
}
@@ -453,19 +415,18 @@ object Utils {
/**
* tcping
*/
suspend fun tcping(url: String, port: Int): String {
suspend fun tcping(url: String, port: Int): Long {
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) {
if (one != -1L && (time == -1L || one < time)) {
time = one
}
}
return time.toString() + "ms"
return time
}
fun socketConnectTime(url: String, port: Int): Long {
@@ -500,5 +461,21 @@ object Utils {
tcpTestingSockets.clear()
}
}
@Throws(IOException::class)
fun getUrlContentWithCustomUserAgent(urlStr: String?): String {
val url = URL(urlStr)
val conn = url.openConnection()
conn.setRequestProperty("Connection", "close")
conn.setRequestProperty("User-agent", "v2rayNG/${BuildConfig.VERSION_NAME}")
url.userInfo?.let {
conn.setRequestProperty("Authorization",
"Basic ${encode(urlDecode(it))}")
}
conn.useCaches = false
return conn.inputStream.use {
it.bufferedReader().readText()
}
}
}

View File

@@ -4,166 +4,121 @@ import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.google.gson.*
import com.v2ray.ang.AngApplication
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.dto.AngConfig.VmessBean
import com.v2ray.ang.AppConfig.ANG_PACKAGE
import com.v2ray.ang.dto.V2rayConfig
import com.v2ray.ang.ui.SettingsActivity
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONArray
import com.v2ray.ang.dto.EConfigType
import com.v2ray.ang.extension.defaultDPreference
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashSet
import com.v2ray.ang.dto.V2rayConfig.Companion.DEFAULT_NETWORK
import com.v2ray.ang.dto.V2rayConfig.Companion.HTTP
object V2rayConfigUtil {
private val requestObj: JsonObject by lazy {
Gson().fromJson("""{"version":"1.1","method":"GET","path":["/"],"headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""", JsonObject::class.java)
}
// private val responseObj: JSONObject by lazy {
// JSONObject("""{"version":"1.1","status":"200","reason":"OK","headers":{"Content-Type":["application/octet-stream","video/mpeg"],"Transfer-Encoding":["chunked"],"Connection":["keep-alive"],"Pragma":"no-cache"}}""")
// }
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
data class Result(var status: Boolean, var content: String)
/**
* 生成v2ray的客户端配置文件
*/
fun getV2rayConfig(app: AngApplication, vmess: VmessBean): Result {
var result = Result(false, "")
fun getV2rayConfig(context: Context, guid: String): Result {
try {
//检查设置
// if (config.index < 0
// || config.vmess.count() <= 0
// || config.index > config.vmess.count() - 1
// ) {
// return result
// }
if (vmess.configType == EConfigType.CUSTOM.value) {
result = getV2rayConfigType2(app, vmess)
} else {
result = getV2rayConfigType1(app, vmess)
val config = MmkvManager.decodeServerConfig(guid) ?: return Result(false, "")
if (config.configType == EConfigType.CUSTOM) {
val raw = serverRawStorage?.decodeString(guid)
val customConfig = if (raw.isNullOrBlank()) {
config.fullConfig?.toPrettyPrinting() ?: return Result(false, "")
} else {
raw
}
Log.d(ANG_PACKAGE, customConfig)
return Result(true, customConfig)
}
Log.d("V2rayConfigUtilGoLog", result.content)
val outbound = config.getProxyOutbound() ?: return Result(false, "")
val result = getV2rayNonCustomConfig(context, outbound)
Log.d(ANG_PACKAGE, result.content)
return result
} catch (e: Exception) {
e.printStackTrace()
return result
}
}
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的客户端配置文件
*/
private fun getV2rayConfigType1(app: AngApplication, vmess: VmessBean): Result {
val result = Result(false, "")
try {
//取得默认配置
val assets = Utils.readTextFromAssets(app, "v2ray_config.json")
if (TextUtils.isEmpty(assets)) {
return result
}
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
// if (v2rayConfig == null) {
// return result
// }
inbounds(vmess, v2rayConfig, app)
outbounds(vmess, v2rayConfig, app)
routing(vmess, v2rayConfig, app)
if (app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_LOCAL_DNS_ENABLED, false)) {
customLocalDns(vmess, v2rayConfig, app)
} else {
customRemoteDns(vmess, v2rayConfig, app)
}
val finalConfig = GsonBuilder().setPrettyPrinting().create().toJson(v2rayConfig)
result.status = true
result.content = finalConfig
return result
} catch (e: Exception) {
e.printStackTrace()
return result
return Result(false, "")
}
}
/**
* 生成v2ray的客户端配置文件
*/
private fun getV2rayConfigType2(app: AngApplication, vmess: VmessBean): Result {
private fun getV2rayNonCustomConfig(context: Context, outbound: V2rayConfig.OutboundBean): Result {
val result = Result(false, "")
try {
val guid = vmess.guid
val jsonConfig = app.defaultDPreference.getPrefString(AppConfig.ANG_CONFIG + guid, "")
result.status = true
result.content = jsonConfig
parseDomainNameAndTag(app, jsonConfig)
return result
} catch (e: Exception) {
e.printStackTrace()
//取得默认配置
val assets = Utils.readTextFromAssets(context, "v2ray_config.json")
if (TextUtils.isEmpty(assets)) {
return result
}
//转成Json
val v2rayConfig = Gson().fromJson(assets, V2rayConfig::class.java) ?: return result
//v2rayConfig.log.loglevel = settingsStorage?.decodeString(AppConfig.PREF_LOGLEVEL) ?: "warning"
inbounds(v2rayConfig)
httpRequestObject(outbound)
v2rayConfig.outbounds[0] = outbound
routing(v2rayConfig)
fakedns(v2rayConfig)
dns(v2rayConfig)
if (settingsStorage?.decodeBool(AppConfig.PREF_LOCAL_DNS_ENABLED) == true) {
customLocalDns(v2rayConfig)
}
if (settingsStorage?.decodeBool(AppConfig.PREF_SPEED_ENABLED) != true) {
v2rayConfig.stats = null
v2rayConfig.policy = null
}
result.status = true
result.content = v2rayConfig.toPrettyPrinting()
return result
}
/**
*
*/
private fun inbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun inbounds(v2rayConfig: V2rayConfig): Boolean {
try {
//val socksPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_SOCKS_PORT) ?: AppConfig.PORT_SOCKS)
//val httpPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_HTTP_PORT) ?: AppConfig.PORT_HTTP)
v2rayConfig.inbounds.forEach { curInbound ->
if (!app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_PROXY_SHARING, false)) {
if (settingsStorage?.decodeBool(AppConfig.PREF_PROXY_SHARING) != true) {
//bind all inbounds to localhost if the user requests
curInbound.listen = "127.0.0.1"
}
}
v2rayConfig.inbounds[0].port = 10808
// val socksPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_SOCKS_PORT, "10808"))
// val lanconnPort = Utils.parseInt(app.defaultDPreference.getPrefString(SettingsActivity.PREF_HTTP_PORT, ""))
val fakedns = settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED)
?: false
val sniffAllTlsAndHttp = settingsStorage?.decodeBool(AppConfig.PREF_SNIFFING_ENABLED, true)
?: true
v2rayConfig.inbounds[0].sniffing?.enabled = fakedns || sniffAllTlsAndHttp
if (!sniffAllTlsAndHttp) {
v2rayConfig.inbounds[0].sniffing?.destOverride?.clear()
}
if (fakedns) {
v2rayConfig.inbounds[0].sniffing?.destOverride?.add("fakedns")
}
// if (socksPort > 0) {
// v2rayConfig.inbounds[0].port = socksPort
// }
// if (lanconnPort > 0) {
//v2rayConfig.inbounds[1].port = httpPort
// if (httpPort > 0) {
// val httpCopy = v2rayConfig.inbounds[0].copy()
// httpCopy.port = lanconnPort
// httpCopy.port = httpPort
// httpCopy.protocol = "http"
// v2rayConfig.inbounds.add(httpCopy)
// }
v2rayConfig.inbounds[0].sniffing?.enabled = app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_SNIFFING_ENABLED, true)
} catch (e: Exception) {
e.printStackTrace()
return false
@@ -171,230 +126,39 @@ object V2rayConfigUtil {
return true
}
/**
* vmess协议服务器配置
*/
private fun outbounds(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
try {
val outbound = v2rayConfig.outbounds[0]
val configType = EConfigType.fromInt(vmess.configType)
if (configType != null) {
outbound.protocol = configType.name.toLowerCase()
private fun fakedns(v2rayConfig: V2rayConfig) {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
v2rayConfig.fakedns = listOf(V2rayConfig.FakednsBean())
v2rayConfig.outbounds.filter { it.protocol == "freedom" }.forEach {
it.settings?.domainStrategy = "UseIP"
}
when (configType) {
EConfigType.VMESS -> {
outbound.settings?.servers = null
val vnext = v2rayConfig.outbounds[0].settings?.vnext?.get(0)
vnext?.address = vmess.address
vnext?.port = vmess.port
val user = vnext?.users?.get(0)
user?.id = vmess.id
user?.alterId = vmess.alterId
user?.security = vmess.security
user?.level = 8
//Mux
val muxEnabled = false//app.defaultDPreference.getPrefBoolean(SettingsActivity.PREF_MUX_ENABLED, false)
outbound.mux?.enabled = muxEnabled
//远程服务器底层传输配置
outbound.streamSettings = boundStreamSettings(vmess)
}
EConfigType.SHADOWSOCKS -> {
outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0)
server?.address = vmess.address
server?.method = vmess.security
server?.ota = false
server?.password = vmess.id
server?.port = vmess.port
server?.level = 8
//Mux
outbound.mux?.enabled = false
}
EConfigType.SOCKS -> {
outbound.settings?.vnext = null
val server = outbound.settings?.servers?.get(0)
server?.address = vmess.address
server?.port = vmess.port
//Mux
outbound.mux?.enabled = false
}
}
val serverDomain = if (Utils.isIpv6Address(vmess.address)) {
String.format("[%s]:%s", vmess.address, vmess.port)
} else {
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
}
return true
}
/**
* 远程服务器底层传输配置
*/
private fun boundStreamSettings(vmess: VmessBean): V2rayConfig.OutboundBean.StreamSettingsBean {
val streamSettings = V2rayConfig.OutboundBean.StreamSettingsBean("", "", null, null, null, null, null, null)
try {
//远程服务器底层传输配置
streamSettings.network = vmess.network
streamSettings.security = vmess.streamSecurity
//streamSettings
when (streamSettings.network) {
"kcp" -> {
val kcpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean()
kcpsettings.mtu = 1350
kcpsettings.tti = 50
kcpsettings.uplinkCapacity = 12
kcpsettings.downlinkCapacity = 100
kcpsettings.congestion = false
kcpsettings.readBufferSize = 1
kcpsettings.writeBufferSize = 1
kcpsettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.KcpSettingsBean.HeaderBean()
kcpsettings.header.type = vmess.headerType
streamSettings.kcpSettings = kcpsettings
}
"ws" -> {
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.Host = host
}
if (!TextUtils.isEmpty(path)) {
wssettings.path = path
}
streamSettings.wsSettings = wssettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true
if (!TextUtils.isEmpty(host)) {
tlssettings.serverName = host
}
streamSettings.tlsSettings = tlssettings
}
"h2" -> {
val httpsettings = V2rayConfig.OutboundBean.StreamSettingsBean.HttpSettingsBean()
val host = vmess.requestHost.trim()
val path = vmess.path.trim()
if (!TextUtils.isEmpty(host)) {
httpsettings.host = host.split(",").map { it.trim() }
}
httpsettings.path = path
streamSettings.httpSettings = httpsettings
val tlssettings = V2rayConfig.OutboundBean.StreamSettingsBean.TlsSettingsBean()
tlssettings.allowInsecure = true
streamSettings.tlsSettings = tlssettings
}
"quic" -> {
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.type = vmess.headerType
streamSettings.quicSettings = quicsettings
}
else -> {
//tcp带http伪装
if (vmess.headerType == "http") {
val tcpSettings = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean()
tcpSettings.header = V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean()
tcpSettings.header.type = vmess.headerType
// if (requestObj.has("headers")
// || requestObj.optJSONObject("headers").has("Pragma")) {
// val arrHost = ArrayList<String>()
// vmess.requestHost
// .split(",")
// .forEach {
// arrHost.add(it)
// }
// requestObj.optJSONObject("headers")
// .put("Host", arrHost)
//
// }
if (!TextUtils.isEmpty(vmess.requestHost)) {
val arrHost = ArrayList<String>()
vmess.requestHost
.split(",")
.forEach {
arrHost.add("\"$it\"")
}
requestObj.getAsJsonObject("headers")
.add("Host", Gson().fromJson(arrHost.toString(), JsonArray::class.java))
}
if (!TextUtils.isEmpty(vmess.path)) {
val arrPath = ArrayList<String>()
vmess.path
.split(",")
.forEach {
arrPath.add("\"$it\"")
}
requestObj.add("path", Gson().fromJson(arrPath.toString(), JsonArray::class.java))
}
tcpSettings.header.request = requestObj
//tcpSettings.header.response = responseObj
streamSettings.tcpSettings = tcpSettings
}
}
}
} catch (e: Exception) {
e.printStackTrace()
return streamSettings
}
return streamSettings
}
/**
* routing
*/
private fun routing(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun routing(v2rayConfig: V2rayConfig): Boolean {
try {
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""), AppConfig.TAG_AGENT, v2rayConfig)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""), AppConfig.TAG_DIRECT, v2rayConfig)
routingUserRule(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""), AppConfig.TAG_BLOCKED, v2rayConfig)
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "", AppConfig.TAG_AGENT, v2rayConfig)
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "", AppConfig.TAG_DIRECT, v2rayConfig)
routingUserRule(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "", AppConfig.TAG_BLOCKED, v2rayConfig)
v2rayConfig.routing.domainStrategy = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_DOMAIN_STRATEGY, "IPIfNonMatch")
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
v2rayConfig.routing.domainStrategy = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_DOMAIN_STRATEGY)
?: "IPIfNonMatch"
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
// Hardcode googleapis.cn
val googleapisRoute = V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_AGENT,
domain = arrayListOf("domain:googleapis.cn")
type = "field",
outboundTag = AppConfig.TAG_AGENT,
domain = arrayListOf("domain:googleapis.cn")
)
when (routingMode) {
"0" -> {
}
"1" -> {
routingGeo("ip", "private", AppConfig.TAG_DIRECT, v2rayConfig)
}
@@ -423,7 +187,7 @@ object V2rayConfigUtil {
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
rulesIP.type = "field"
rulesIP.outboundTag = tag
rulesIP.ip = ArrayList<String>()
rulesIP.ip = ArrayList()
rulesIP.ip?.add("geoip:$code")
v2rayConfig.routing.rules.add(rulesIP)
}
@@ -433,7 +197,7 @@ object V2rayConfigUtil {
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
rulesDomain.type = "field"
rulesDomain.outboundTag = tag
rulesDomain.domain = ArrayList<String>()
rulesDomain.domain = ArrayList()
rulesDomain.domain?.add("geosite:$code")
v2rayConfig.routing.rules.add(rulesDomain)
}
@@ -450,29 +214,27 @@ object V2rayConfigUtil {
val rulesDomain = V2rayConfig.RoutingBean.RulesBean()
rulesDomain.type = "field"
rulesDomain.outboundTag = tag
rulesDomain.domain = ArrayList<String>()
rulesDomain.domain = ArrayList()
//IP
val rulesIP = V2rayConfig.RoutingBean.RulesBean()
rulesIP.type = "field"
rulesIP.outboundTag = tag
rulesIP.ip = ArrayList<String>()
rulesIP.ip = ArrayList()
userRule.trim().replace("\n", "")
.split(",")
.forEach {
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
rulesIP.ip?.add(it)
} else if (it.isNotBlank() || it.isNotEmpty())
userRule.split(",").map { it.trim() }.forEach {
if (Utils.isIpAddress(it) || it.startsWith("geoip:")) {
rulesIP.ip?.add(it)
} else if (it.isNotEmpty())
// if (Utils.isValidUrl(it)
// || it.startsWith("geosite:")
// || it.startsWith("regexp:")
// || it.startsWith("domain:")
// || it.startsWith("full:"))
{
rulesDomain.domain?.add(it)
}
}
{
rulesDomain.domain?.add(it)
}
}
if (rulesDomain.domain?.size!! > 0) {
v2rayConfig.routing.rules.add(rulesDomain)
}
@@ -487,9 +249,8 @@ object V2rayConfigUtil {
private fun userRule2Domian(userRule: String): ArrayList<String> {
val domain = ArrayList<String>()
userRule.trim().replace("\n", "").split(",").forEach {
if ((it.startsWith("geosite:") || it.startsWith("domain:")) &&
it.isNotBlank() && it.isNotEmpty()) {
userRule.split(",").map { it.trim() }.forEach {
if (it.startsWith("geosite:") || it.startsWith("domain:")) {
domain.add(it)
}
}
@@ -499,98 +260,127 @@ object V2rayConfigUtil {
/**
* Custom Dns
*/
private fun customLocalDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun customLocalDns(v2rayConfig: V2rayConfig): Boolean {
try {
if (settingsStorage?.decodeBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
val geositeCn = arrayListOf("geosite:cn")
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "")
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "")
// fakedns with all domains to make it always top priority
v2rayConfig.dns.servers?.add(0,
V2rayConfig.DnsBean.ServersBean(address = "fakedns", domains = geositeCn.plus(proxyDomain).plus(directDomain)))
}
// DNS inbound对象
val remoteDns = Utils.getRemoteDnsServers()
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = if (Utils.isPureIpAddress(remoteDns.first())) remoteDns.first() else "1.1.1.1",
port = 53,
network = "tcp,udp")
//val localDnsPort = Utils.parseInt(settingsStorage?.decodeString(AppConfig.PREF_LOCAL_DNS_PORT) ?: AppConfig.PORT_LOCAL_DNS)
v2rayConfig.inbounds.add(
V2rayConfig.InboundBean(
tag = "dns-in",
port = 10807,
listen = "127.0.0.1",
protocol = "dokodemo-door",
settings = dnsInboundSettings,
sniffing = null))
}
// DNS outbound对象
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
v2rayConfig.outbounds.add(
V2rayConfig.OutboundBean(
protocol = "dns",
tag = "dns-out",
settings = null,
streamSettings = null,
mux = null))
}
// DNS routing tag
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
inboundTag = arrayListOf("dns-in"),
outboundTag = "dns-out",
domain = null)
)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
private fun dns(v2rayConfig: V2rayConfig): Boolean {
try {
val hosts = mutableMapOf<String, String>()
val servers = ArrayList<Any>()
val remoteDns = Utils.getRemoteDnsServers(app.defaultDPreference)
val remoteDns = Utils.getRemoteDnsServers()
val proxyDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_AGENT)
?: "")
remoteDns.forEach {
servers.add(it)
}
val domesticDns = Utils.getDomesticDnsServers(app.defaultDPreference)
val agDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_AGENT, ""))
if (agDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, agDomain))
if (proxyDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(remoteDns.first(), 53, proxyDomain, null))
}
val dirDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_DIRECT, ""))
if (dirDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, dirDomain))
// domestic DNS
val directDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_DIRECT)
?: "")
val routingMode = settingsStorage?.decodeString(AppConfig.PREF_ROUTING_MODE) ?: "0"
if (directDomain.size > 0 || routingMode == "2" || routingMode == "3") {
val domesticDns = Utils.getDomesticDnsServers()
val geositeCn = arrayListOf("geosite:cn")
val geoipCn = arrayListOf("geoip:cn")
if (directDomain.size > 0) {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, directDomain, geoipCn))
}
if (routingMode == "2" || routingMode == "3") {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, geositeCn, geoipCn))
}
if (Utils.isPureIpAddress(domesticDns.first())) {
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = arrayListOf(domesticDns.first()),
domain = null)
)
}
}
val routingMode = app.defaultDPreference.getPrefString(SettingsActivity.PREF_ROUTING_MODE, "0")
if (routingMode == "2" || routingMode == "3") {
servers.add(V2rayConfig.DnsBean.ServersBean(domesticDns.first(), 53, arrayListOf("geosite:cn")))
}
val blkDomain = userRule2Domian(app.defaultDPreference.getPrefString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED, ""))
val blkDomain = userRule2Domian(settingsStorage?.decodeString(AppConfig.PREF_V2RAY_ROUTING_BLOCKED)
?: "")
if (blkDomain.size > 0) {
hosts.putAll(blkDomain.map { it to "127.0.0.1" })
}
// hardcode googleapi rule to fix play store problems
hosts.put("domain:googleapis.cn", "googleapis.com")
hosts["domain:googleapis.cn"] = "googleapis.com"
// DNS dns对象
v2rayConfig.dns = V2rayConfig.DnsBean(
servers = servers,
hosts = hosts)
// DNS inbound对象
if (v2rayConfig.inbounds.none { e -> e.protocol == "dokodemo-door" && e.tag == "dns-in" }) {
val dnsInboundSettings = V2rayConfig.InboundBean.InSettingsBean(
address = remoteDns.first(),
port = 53,
network = "tcp,udp")
v2rayConfig.inbounds.add(
V2rayConfig.InboundBean(
tag = "dns-in",
port = 10807,
listen = "127.0.0.1",
protocol = "dokodemo-door",
settings = dnsInboundSettings,
sniffing = null))
}
// DNS outbound对象
if (v2rayConfig.outbounds.none { e -> e.protocol == "dns" && e.tag == "dns-out" }) {
v2rayConfig.outbounds.add(
V2rayConfig.OutboundBean(
protocol = "dns",
tag = "dns-out",
settings = null,
streamSettings = null,
mux = null))
}
// DNS routing
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_DIRECT,
port = "53",
ip = domesticDns,
domain = null)
)
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_AGENT,
port = "53",
ip = remoteDns,
domain = null)
)
// DNS routing tag
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
inboundTag = arrayListOf<String>("dns-in"),
outboundTag = "dns-out",
domain = null)
)
if (Utils.isPureIpAddress(remoteDns.first())) {
v2rayConfig.routing.rules.add(0, V2rayConfig.RoutingBean.RulesBean(
type = "field",
outboundTag = AppConfig.TAG_AGENT,
port = "53",
ip = arrayListOf(remoteDns.first()),
domain = null)
)
}
} catch (e: Exception) {
e.printStackTrace()
return false
@@ -598,115 +388,34 @@ object V2rayConfigUtil {
return true
}
/**
* Custom Remote Dns
*/
private fun customRemoteDns(vmess: VmessBean, v2rayConfig: V2rayConfig, app: AngApplication): Boolean {
private fun httpRequestObject(outbound: V2rayConfig.OutboundBean): Boolean {
try {
val servers = ArrayList<Any>()
if (outbound.streamSettings?.network == DEFAULT_NETWORK
&& outbound.streamSettings?.tcpSettings?.header?.type == HTTP) {
val path = outbound.streamSettings?.tcpSettings?.header?.request?.path
val host = outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host
Utils.getRemoteDnsServers(app.defaultDPreference).forEach {
servers.add(it)
}
v2rayConfig.dns = V2rayConfig.DnsBean(servers = servers)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
/**
* is valid config
*/
fun isValidConfig(conf: String): Boolean {
try {
val jObj = JSONObject(conf)
var hasBound = false
//hasBound = (jObj.has("outbounds") and jObj.has("inbounds")) or (jObj.has("outbound") and jObj.has("inbound"))
hasBound = (jObj.has("outbounds")) or (jObj.has("outbound"))
return hasBound
} catch (e: JSONException) {
return false
}
}
private fun parseDomainNameAndTag(app: AngApplication, jsonConfig: String) {
try {
val jObj = JSONObject(jsonConfig)
var domainName = ""
val tags = LinkedHashSet<String>()
if (jObj.has("outbound")) {
val (domain, tag) = parseDomainNameAndTag(jObj.optJSONObject("outbound"))
domainName = domain
if (!TextUtils.isEmpty(tag)) {
tags.add(tag)
val requestString: String by lazy {
"""{"version":"1.1","method":"GET","headers":{"User-Agent":["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36","Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46"],"Accept-Encoding":["gzip, deflate"],"Connection":["keep-alive"],"Pragma":"no-cache"}}"""
}
}
if (jObj.has("outbounds")) {
for (i in 0..(jObj.optJSONArray("outbounds").length() - 1)) {
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)) {
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()
}
}
private fun parseDomainNameAndTag(outbound: JSONObject): Pair<String, String> {
val tag = if (outbound.has("tag")) {
outbound.getString("tag")
} else {
""
}
try {
if (outbound.has("settings")) {
val vnext: JSONArray?
if (outbound.optJSONObject("settings").has("vnext")) {
// vmess
vnext = outbound.optJSONObject("settings").optJSONArray("vnext")
} else if (outbound.optJSONObject("settings").has("servers")) {
// shadowsocks or socks
vnext = outbound.optJSONObject("settings").optJSONArray("servers")
} else {
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 Pair(String.format("[%s]:%s", address, port), tag)
outbound.streamSettings?.tcpSettings?.header?.request = Gson().fromJson(
requestString,
V2rayConfig.OutboundBean.StreamSettingsBean.TcpSettingsBean.HeaderBean.RequestBean::class.java
)
outbound.streamSettings?.tcpSettings?.header?.request?.path =
if (path.isNullOrEmpty()) {
listOf("/")
} else {
return Pair(String.format("%s:%s", address, port), tag)
path
}
}
outbound.streamSettings?.tcpSettings?.header?.request?.headers?.Host = host!!
}
} catch (e: Exception) {
e.printStackTrace()
return false
}
return Pair("", tag)
return true
}
}

View File

@@ -1,25 +1,35 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.MutableLiveData
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import com.google.gson.Gson
import com.tencent.mmkv.MMKV
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.dto.ServerConfig
import com.v2ray.ang.dto.V2rayConfig
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 com.v2ray.ang.util.*
import com.v2ray.ang.util.MmkvManager.KEY_ANG_CONFIGS
import kotlinx.coroutines.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val mainStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_MAIN, MMKV.MULTI_PROCESS_MODE) }
private val serverRawStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SERVER_RAW, MMKV.MULTI_PROCESS_MODE) }
var serverList = MmkvManager.decodeServerList()
private set
val serversCache = ConcurrentHashMap<String, ServerConfig>()
val isRunning by lazy { MutableLiveData<Boolean>() }
val updateListAction by lazy { MutableLiveData<Int>() }
val updateTestResultAction by lazy { MutableLiveData<String>() }
@@ -34,31 +44,67 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
override fun onCleared() {
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
Utils.closeAllTcpSockets()
Log.i(AppConfig.ANG_PACKAGE, "Main ViewModel is cleared")
super.onCleared()
}
fun reloadServerList() {
serverList = MmkvManager.decodeServerList()
updateCache()
updateListAction.value = -1
}
fun removeServer(guid: String) {
serverList.remove(guid)
MmkvManager.removeServer(guid)
}
fun appendCustomConfigServer(server: String) {
val config = ServerConfig.create(EConfigType.CUSTOM)
config.remarks = System.currentTimeMillis().toString()
config.fullConfig = Gson().fromJson(server, V2rayConfig::class.java)
val key = MmkvManager.encodeServerConfig("", config)
serverRawStorage?.encode(key, server)
serverList.add(key)
serversCache[key] = config
}
fun swapServer(fromPosition: Int, toPosition: Int) {
Collections.swap(serverList, fromPosition, toPosition)
mainStorage?.encode(KEY_ANG_CONFIGS, Gson().toJson(serverList))
}
fun updateCache() {
serversCache.clear()
GlobalScope.launch(Dispatchers.Default) {
serverList.forEach { guid ->
MmkvManager.decodeServerConfig(guid)?.let {
serversCache[guid] = it
}
}
}
}
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
MmkvManager.clearAllTestDelayResults()
updateListAction.value = -1 // update all
getApplication<AngApplication>().toast(R.string.connection_test_testing)
for (guid in serverList) {
serversCache.getOrElse(guid) { MmkvManager.decodeServerConfig(guid) }?.getProxyOutbound()?.let { outbound ->
val serverAddress = outbound.getServerAddress()
val serverPort = outbound.getServerPort()
if (serverAddress != null && serverPort != null) {
tcpingTestScope.launch {
val testResult = Utils.tcping(serverAddress, serverPort)
launch(Dispatchers.Main) {
MmkvManager.encodeServerTestDelayMillis(guid, testResult)
updateListAction.value = serverList.indexOf(guid)
}
}
}
}

View File

@@ -0,0 +1,57 @@
package com.v2ray.ang.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import android.util.Log
import com.tencent.mmkv.MMKV
import com.v2ray.ang.AppConfig
import com.v2ray.ang.util.MmkvManager
class SettingsViewModel(application: Application) : AndroidViewModel(application), SharedPreferences.OnSharedPreferenceChangeListener {
private val settingsStorage by lazy { MMKV.mmkvWithID(MmkvManager.ID_SETTING, MMKV.MULTI_PROCESS_MODE) }
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) {
AppConfig.PREF_MODE,
AppConfig.PREF_VPN_DNS,
AppConfig.PREF_REMOTE_DNS,
AppConfig.PREF_DOMESTIC_DNS,
AppConfig.PREF_ROUTING_DOMAIN_STRATEGY,
AppConfig.PREF_ROUTING_MODE,
AppConfig.PREF_V2RAY_ROUTING_AGENT,
AppConfig.PREF_V2RAY_ROUTING_BLOCKED,
AppConfig.PREF_V2RAY_ROUTING_DIRECT, -> {
settingsStorage?.encode(key, sharedPreferences.getString(key, ""))
}
AppConfig.PREF_SPEED_ENABLED,
AppConfig.PREF_PROXY_SHARING,
AppConfig.PREF_LOCAL_DNS_ENABLED,
AppConfig.PREF_FAKE_DNS_ENABLED,
AppConfig.PREF_FORWARD_IPV6,
AppConfig.PREF_PER_APP_PROXY,
AppConfig.PREF_BYPASS_APPS, -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, false))
}
AppConfig.PREF_SNIFFING_ENABLED -> {
settingsStorage?.encode(key, sharedPreferences.getBoolean(key, true))
}
AppConfig.PREF_PER_APP_PROXY_SET -> {
settingsStorage?.encode(key, sharedPreferences.getStringSet(key, setOf()))
}
}
}
}

View File

@@ -13,17 +13,17 @@
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
app:layoutManager="android.support.v7.widget.LinearLayoutManager"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:context=".ui.PerAppProxyActivity" />
<android.support.v7.widget.LinearLayoutCompat
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/header_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/bypass_list_header_height">
<android.support.v7.widget.SwitchCompat
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_per_app_proxy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -45,7 +45,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
<android.support.v7.widget.AppCompatTextView
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
@@ -63,7 +63,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/bypass_list_header_height">
<android.support.v7.widget.SwitchCompat
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_bypass_apps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -71,7 +71,7 @@
android:layout_alignParentRight="true"
android:layout_centerVertical="true" />
<android.support.v7.widget.AppCompatTextView
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_bypass_apps"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -89,11 +89,11 @@
android:layout_width="match_parent"
android:layout_height="@dimen/bypass_list_header_height">
<android.support.design.widget.TextInputLayout
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.AppCompatEditText
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/et_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -103,11 +103,11 @@
android:maxLines="1"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</android.support.design.widget.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
</RelativeLayout>
</android.support.v7.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</RelativeLayout>
</RelativeLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
@@ -12,19 +12,19 @@
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -34,7 +34,7 @@
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -44,11 +44,12 @@
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
android:layout_weight="1"
android:nextFocusRight="@+id/fab" />
<LinearLayout
android:id="@+id/layout_test"
@@ -82,7 +83,7 @@
android:layout_marginBottom="24dp"
android:layout_gravity="bottom|end">
<android.support.design.widget.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -96,14 +97,15 @@
android:src="@drawable/ic_v_idle"
android:clickable="true"
android:focusable="true"
app:layout_anchorGravity="bottom|right|end" />
app:layout_anchorGravity="bottom|right|end"
android:nextFocusLeft="@+id/recycler_view" />
</com.github.jorgecastilloprz.FABProgressCircle>
</android.support.design.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>
</LinearLayout>
<android.support.design.widget.NavigationView
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -127,7 +129,7 @@
android:gravity="center"
android:textColor="@color/accent" />
</LinearLayout>
</android.support.design.widget.NavigationView>
</com.google.android.material.navigation.NavigationView>
</android.support.v4.widget.DrawerLayout>
</androidx.drawerlayout.widget.DrawerLayout>

View File

@@ -4,14 +4,14 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<android.support.design.widget.TabLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/tablayout"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v4.view.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout>

View File

@@ -8,14 +8,13 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/layout_margin_top_height">
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/layout_margin_top_height">
android:layout_margin="@dimen/layout_margin_spacing">
<TextView
android:layout_width="wrap_content"
@@ -28,7 +27,8 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:layout_margin="@dimen/layout_margin_spacing">
<TextView
android:layout_width="wrap_content"
@@ -40,7 +40,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:layout_margin="@dimen/layout_margin_spacing"
android:orientation="vertical">
<TextView
@@ -59,24 +59,24 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/layout_margin_spacing"
android:text="@string/server_lab_content"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<EditText
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:text="" />
<com.blacksquircle.ui.editorkit.widget.TextProcessor
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|start"
android:id="@+id/editor"
android:layout_marginTop="@dimen/layout_margin_top_height" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@@ -80,11 +80,51 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="User(Optional)" />
<EditText
android:id="@+id/et_security"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="text" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password(Optional)" />
<EditText
android:id="@+id/et_id"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="textPassword" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/layout_margin_top_height"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@@ -98,26 +98,6 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:id="@+id/tv_alterId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_alterid" />
<EditText
android:id="@+id/et_alterId"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:inputType="number" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -143,28 +123,28 @@
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_network" />
<Spinner
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_more_function"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
android:id="@+id/sp_network"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/networks" />
</LinearLayout>
<LinearLayout
@@ -174,6 +154,7 @@
android:orientation="vertical">
<TextView
android:id="@+id/sp_header_type_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_head_type" />
@@ -181,8 +162,7 @@
<Spinner
android:id="@+id/sp_header_type"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/headertypes" />
android:layout_height="@dimen/edit_height" />
</LinearLayout>
<LinearLayout
@@ -241,6 +221,28 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_margin_top_height"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/server_lab_allow_insecure" />
<Spinner
android:id="@+id/sp_allow_insecure"
android:layout_width="match_parent"
android:layout_height="@dimen/edit_height"
android:entries="@array/allowinsecures" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -249,4 +251,4 @@
android:orientation="vertical" />
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@@ -8,15 +8,15 @@
android:fitsSystemWindows="true"
tools:context=".ui.MainActivity">
<android.support.design.widget.CoordinatorLayout
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.CoordinatorLayout>
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>

View File

@@ -10,7 +10,7 @@
android:layout_width="match_parent"
android:layout_height="@dimen/bypass_list_header_height">
<android.support.v7.widget.SwitchCompat
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switch_start_service"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -19,7 +19,7 @@
android:layout_centerVertical="true"
android:checked="true" />
<android.support.v7.widget.AppCompatTextView
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
@@ -45,4 +45,4 @@
android:choiceMode="singleChoice" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -7,7 +7,7 @@
android:clickable="true"
android:focusable="true">
<android.support.v7.widget.AppCompatImageView
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="46dp"
android:layout_height="46dp"
@@ -25,12 +25,12 @@
android:paddingStart="@dimen/layout_margin_right_height"
android:paddingEnd="@dimen/layout_margin_right_height">
<android.support.v7.widget.AppCompatTextView
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v7.widget.AppCompatTextView
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/package_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -38,7 +38,7 @@
</LinearLayout>
<android.support.v7.widget.AppCompatCheckBox
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/check_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -49,4 +49,4 @@
android:paddingEnd="6dp"
android:paddingRight="6dp" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,15 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_bg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
card_view:cardCornerRadius="5dp">
app:cardCornerRadius="5dp">
<LinearLayout
android:id="@+id/info_container"
@@ -18,34 +20,19 @@
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:nextFocusRight="@+id/layout_share">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_subid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<android.support.v7.widget.AppCompatRadioButton
<androidx.appcompat.widget.AppCompatRadioButton
android:id="@+id/btn_radio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -85,20 +72,31 @@
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="vertical"
android:paddingEnd="5dp">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingEnd="5dp">
<TextView
android:id="@+id/tv_test_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorPing"
android:textSize="10sp" />
android:id="@+id/tv_subscription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorSubscription"
android:textSize="10sp"
tools:text="Sub" />
<TextView
android:id="@+id/tv_test_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textColor="@color/colorPing"
android:textSize="10sp"
tools:text="214ms" />
</LinearLayout>
<LinearLayout
@@ -184,5 +182,5 @@
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -5,7 +5,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical">
<android.support.v7.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
<androidx.cardview.widget.CardView xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_cardview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -102,5 +102,5 @@
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.appbar.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
<com.google.android.material.navigation.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_view"
android:layout_width="wrap_content"
@@ -8,4 +8,4 @@
app:headerLayout="@layout/nav_header"
app:itemTextColor="@color/colorPrimary"
app:itemIconTint="@color/colorPrimary_dark"
app:menu="@menu/menu_drawer" />
app:menu="@menu/menu_drawer" />

View File

@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.AppCompat.Button.Borderless"
xmlns:tools="http://schemas.android.com/tools"
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" />
android:textStyle="italic"
tools:ignore="UsingOnClickInXml" />

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="summary_pref_per_app_proxy">Set proxy for selected apps</string>
</resources>

View File

@@ -1,8 +0,0 @@
<resources>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
</style>
</resources>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="summary_pref_per_app_proxy">为应用程序分别设置代理</string>
</resources>

View File

@@ -6,6 +6,8 @@
<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>
<string name="migration_success">数据迁移成功!</string>
<string name="migration_fail">数据迁移失败啦!</string>
<!-- Notifications -->
<string name="notification_action_stop_v2ray">停止</string>
@@ -36,15 +38,15 @@
<string name="server_lab_address">地址(address)</string>
<string name="server_lab_port">端口(port)</string>
<string name="server_lab_id">用户ID(id)</string>
<string name="server_lab_alterid">额外ID(alterId)</string>
<string name="server_lab_security">加密方式(security)</string>
<string name="server_lab_network">传输协议(network)</string>
<string name="server_lab_more_function">功能设置(不清楚则保持默认值)</string>
<string name="server_lab_head_type">伪装类型(type)</string>
<string name="server_lab_request_host">伪装域名host(host/ws host/h2 host)/QUIC 加密方式</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥</string>
<string name="server_lab_stream_security">底层传输安全</string>
<string name="server_lab_allow_insecure">allowInsecure</string>
<string name="server_lab_mode_type">gRPC 传输模式 (mode)</string>
<string name="server_lab_request_host">伪装域名(host)(host/ws host/h2 host)/QUIC 加密方式</string>
<string name="server_lab_path">path(ws path/h2 path)/QUIC 加密密钥/kcp seed/gRPC serviceName</string>
<string name="server_lab_stream_security">底层传输安全(tls)</string>
<string name="server_lab_allow_insecure">跳过证书验证(allowInsecure)</string>
<string name="server_lab_address3">服务器地址</string>
<string name="server_lab_port3">服务器端口</string>
<string name="server_lab_id3">密码</string>
@@ -74,9 +76,9 @@
<!-- Preferences -->
<string name="title_settings">设置</string>
<string name="title_advanced">进阶设置</string>
<string name="title_vpn_settings">VPN 设置</string>
<string name="title_pref_per_app_proxy">分应用代理</string>
<string name="summary_pref_per_app_proxy">分应用代理仅支持 Android 5.0 Lollipop 及更高</string>
<string name="summary_pref_per_app_proxy">常规:勾选的App被代理,未勾选的直连;\n绕行模式:勾选的App直连,未勾选的被代理.\n不明白者在菜单中选择自动选中需代理应用</string>
<string name="title_pref_mux_enabled">启用Mux多路复用</string>
<string name="summary_pref_mux_enabled">开启可能会加速,关闭可能会减少断流</string>
@@ -85,10 +87,13 @@
<string name="summary_pref_speed_enabled">在通知中显示当前速度\n小图标显示流量的路由情况</string>
<string name="title_pref_sniffing_enabled">启用流量探测</string>
<string name="summary_pref_sniffing_enabled">流量探测</string>
<string name="summary_pref_sniffing_enabled">流量探测域名 (默认启用)</string>
<string name="title_pref_local_dns_enabled">启用本地DNS</string>
<string name="summary_pref_local_dns_enabled">DNS由本地设备的v2Ray-core处理测试</string>
<string name="summary_pref_local_dns_enabled">DNS 请求导入 core 由 DNS 模块处理(推荐启用 如果需要路由绕过局域网及大陆地址)</string>
<string name="title_pref_fake_dns_enabled">启用虚拟DNS</string>
<string name="summary_pref_fake_dns_enabled">本地返回虚构解析结果 (减低延时 但个别应用可能无法使用)</string>
<string name="title_pref_forward_ipv6">IPv6 路由</string>
<string name="summary_pref_forward_ipv6">转发 IPv6 流量到远程服务器(测试)</string>
@@ -105,7 +110,9 @@
<string name="title_pref_remote_dns">远程DNS (可选)</string>
<string name="summary_pref_remote_dns">DNS</string>
<string name="title_pref_domestic_dns">境内DNS (可选本地DNS模式下生效)</string>
<string name="title_pref_vpn_dns">VPN DNS (仅支持 IPv4/v6)</string>
<string name="title_pref_domestic_dns">境内DNS (可选)</string>
<string name="summary_pref_domestic_dns">DNS</string>
<string name="title_pref_socks_port">SOCKS5代理端口</string>
@@ -114,6 +121,9 @@
<string name="title_pref_http_port">HTTP代理端口</string>
<string name="summary_pref_http_port">HTTP代理端口</string>
<string name="title_pref_local_dns_port">本地DNS端口</string>
<string name="summary_pref_local_dns_port">本地DNS端口</string>
<string name="title_pref_feedback">反馈</string>
<string name="summary_pref_feedback">反馈改进或漏洞至 GitHub</string>
<string name="summary_pref_tg_group">加入Telegram Group</string>

Some files were not shown because too many files have changed in this diff Show More